@oh-my-pi/pi-utils 17.4.0 → 17.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +22 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/ar/arj.d.ts +5 -0
- package/dist/types/ar/asar.d.ts +7 -0
- package/dist/types/ar/bytes.d.ts +20 -0
- package/dist/types/ar/cab.d.ts +5 -0
- package/dist/types/ar/checksums.d.ts +10 -0
- package/dist/types/ar/codecs/bzip2.d.ts +4 -0
- package/dist/types/ar/codecs/gzip.d.ts +6 -0
- package/dist/types/ar/codecs/lzma.d.ts +6 -0
- package/dist/types/ar/codecs/lzw.d.ts +4 -0
- package/dist/types/ar/codecs/lzx.d.ts +7 -0
- package/dist/types/ar/codecs/xz.d.ts +4 -0
- package/dist/types/ar/codecs/zstd.d.ts +6 -0
- package/dist/types/ar/cpio.d.ts +7 -0
- package/dist/types/ar/deb.d.ts +5 -0
- package/dist/types/ar/entries.d.ts +22 -0
- package/dist/types/ar/error.d.ts +8 -0
- package/dist/types/ar/index.d.ts +11 -0
- package/dist/types/ar/iso.d.ts +5 -0
- package/dist/types/ar/limits.d.ts +33 -0
- package/dist/types/ar/lzh.d.ts +7 -0
- package/dist/types/ar/open.d.ts +45 -0
- package/dist/types/ar/paths.d.ts +18 -0
- package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
- package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
- package/dist/types/ar/rar.d.ts +5 -0
- package/dist/types/ar/reader.d.ts +28 -0
- package/dist/types/ar/registry.d.ts +17 -0
- package/dist/types/ar/rpm.d.ts +5 -0
- package/dist/types/ar/sevenzip/decode.d.ts +33 -0
- package/dist/types/ar/sevenzip.d.ts +5 -0
- package/dist/types/ar/source.d.ts +54 -0
- package/dist/types/ar/tar.d.ts +9 -0
- package/dist/types/ar/types.d.ts +99 -0
- package/dist/types/ar/unix-ar.d.ts +7 -0
- package/dist/types/ar/write.d.ts +10 -0
- package/dist/types/ar/zip.d.ts +10 -0
- package/dist/types/postmortem.d.ts +33 -0
- package/package.json +9 -3
- package/src/ar/arj.ts +314 -0
- package/src/ar/asar.ts +480 -0
- package/src/ar/bytes.ts +78 -0
- package/src/ar/cab.ts +377 -0
- package/src/ar/checksums.ts +62 -0
- package/src/ar/codecs/bzip2.ts +489 -0
- package/src/ar/codecs/gzip.ts +25 -0
- package/src/ar/codecs/lzma.ts +437 -0
- package/src/ar/codecs/lzw.ts +191 -0
- package/src/ar/codecs/lzx.ts +366 -0
- package/src/ar/codecs/xz.ts +522 -0
- package/src/ar/codecs/zstd.ts +25 -0
- package/src/ar/cpio.ts +389 -0
- package/src/ar/deb.ts +160 -0
- package/src/ar/entries.ts +97 -0
- package/src/ar/error.ts +11 -0
- package/src/ar/index.ts +16 -0
- package/src/ar/iso.ts +712 -0
- package/src/ar/limits.ts +80 -0
- package/src/ar/lzh.ts +659 -0
- package/src/ar/open.ts +225 -0
- package/src/ar/paths.ts +70 -0
- package/src/ar/rar/rar4-decoder.ts +459 -0
- package/src/ar/rar/rar5-decoder.ts +400 -0
- package/src/ar/rar.ts +735 -0
- package/src/ar/reader.ts +162 -0
- package/src/ar/registry.ts +208 -0
- package/src/ar/rpm.ts +320 -0
- package/src/ar/sevenzip/decode.ts +239 -0
- package/src/ar/sevenzip.ts +634 -0
- package/src/ar/source.ts +190 -0
- package/src/ar/tar.ts +771 -0
- package/src/ar/types.ts +131 -0
- package/src/ar/unix-ar.ts +312 -0
- package/src/ar/write.ts +55 -0
- package/src/ar/zip.ts +719 -0
- package/src/browsers.ts +2 -149
- package/src/docx/converter.ts +9 -9
- package/src/postmortem.ts +74 -0
- package/dist/types/docx/zip.d.ts +0 -6
- package/src/docx/zip.ts +0 -87
package/src/ar/source.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { LRUCache } from "../lru";
|
|
2
|
+
import { ArchiveError } from "./error";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A byte window into an archive — file-backed (lazy, ranged reads) or
|
|
6
|
+
* in-memory. Format readers index through this so ZIP/ASAR/RAR payloads are
|
|
7
|
+
* only read when a member is actually extracted.
|
|
8
|
+
*/
|
|
9
|
+
export interface ByteSource {
|
|
10
|
+
readonly size: number;
|
|
11
|
+
read(start: number, end: number): Promise<Uint8Array>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Reject a nonsensical `[start, end)` range before any read. */
|
|
15
|
+
export function assertValidRange(start: number, end: number): void {
|
|
16
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {
|
|
17
|
+
throw new ArchiveError("Invalid archive range");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Read an exact in-memory range, throwing (not clamping) when it runs past the buffer. */
|
|
22
|
+
export function readMemoryRange(buffer: Uint8Array, start: number, end: number): Uint8Array {
|
|
23
|
+
assertValidRange(start, end);
|
|
24
|
+
if (end > buffer.byteLength) {
|
|
25
|
+
throw new ArchiveError("Invalid archive: truncated data");
|
|
26
|
+
}
|
|
27
|
+
return buffer.subarray(start, end);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Wrap borrowed bytes as a {@link ByteSource}. */
|
|
31
|
+
export function memoryByteSource(buffer: Uint8Array): ByteSource {
|
|
32
|
+
return {
|
|
33
|
+
size: buffer.byteLength,
|
|
34
|
+
async read(start, end) {
|
|
35
|
+
return readMemoryRange(buffer, start, end);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Lazily read ranges of a file on disk as a {@link ByteSource}. */
|
|
41
|
+
export function fileByteSource(filePath: string): ByteSource {
|
|
42
|
+
const file = Bun.file(filePath);
|
|
43
|
+
const size = file.size;
|
|
44
|
+
if (!Number.isSafeInteger(size)) {
|
|
45
|
+
throw new ArchiveError("Archive is too large to read safely");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
size,
|
|
49
|
+
async read(start, end) {
|
|
50
|
+
assertValidRange(start, end);
|
|
51
|
+
const bytes = await file.slice(start, end).bytes();
|
|
52
|
+
if (bytes.byteLength !== end - start) {
|
|
53
|
+
throw new ArchiveError("Invalid archive: truncated data");
|
|
54
|
+
}
|
|
55
|
+
return bytes;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Materialize an entire {@link ByteSource}; use only under a limits check. */
|
|
61
|
+
export async function readAllBytes(source: ByteSource): Promise<Uint8Array> {
|
|
62
|
+
return source.read(0, source.size);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Options for {@link httpByteSource}. */
|
|
66
|
+
export interface HttpByteSourceOptions {
|
|
67
|
+
/** Extra request headers (e.g. authorization). */
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
/** Fetch implementation seam for tests; defaults to global `fetch`. */
|
|
70
|
+
fetch?: typeof fetch;
|
|
71
|
+
/**
|
|
72
|
+
* When the server ignores `Range` (responds 200), the body is buffered in
|
|
73
|
+
* memory instead, capped to this many bytes. Default 256 MiB.
|
|
74
|
+
*/
|
|
75
|
+
maxFallbackBytes?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const HTTP_FALLBACK_CAP = 256 * 1024 * 1024;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A {@link ByteSource} over HTTP(S) range requests, so remote archives can be
|
|
82
|
+
* indexed and read member-by-member without downloading the whole file.
|
|
83
|
+
* Probes with `Range: bytes=0-0`; servers without range support fall back to
|
|
84
|
+
* one bounded full download. Wrap with {@link cachingByteSource} to coalesce
|
|
85
|
+
* the many small header reads format parsers issue.
|
|
86
|
+
*/
|
|
87
|
+
export async function httpByteSource(url: string | URL, options: HttpByteSourceOptions = {}): Promise<ByteSource> {
|
|
88
|
+
const doFetch = options.fetch ?? fetch;
|
|
89
|
+
const headers = { ...options.headers, range: "bytes=0-0" };
|
|
90
|
+
const probe = await doFetch(url, { headers });
|
|
91
|
+
if (probe.status === 200) {
|
|
92
|
+
// No range support: buffer the whole body once, bounded.
|
|
93
|
+
const cap = options.maxFallbackBytes ?? HTTP_FALLBACK_CAP;
|
|
94
|
+
const declared = Number(probe.headers.get("content-length") ?? 0);
|
|
95
|
+
if (declared > cap) {
|
|
96
|
+
throw new ArchiveError(
|
|
97
|
+
`Remote archive is too large to buffer without range support (${declared} > ${cap} bytes)`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const bytes = new Uint8Array(await probe.arrayBuffer());
|
|
101
|
+
if (bytes.byteLength > cap) {
|
|
102
|
+
throw new ArchiveError(`Remote archive is too large to buffer without range support (> ${cap} bytes)`);
|
|
103
|
+
}
|
|
104
|
+
return memoryByteSource(bytes);
|
|
105
|
+
}
|
|
106
|
+
if (probe.status !== 206) {
|
|
107
|
+
await probe.body?.cancel();
|
|
108
|
+
throw new ArchiveError(`Remote archive request failed (HTTP ${probe.status})`);
|
|
109
|
+
}
|
|
110
|
+
await probe.body?.cancel();
|
|
111
|
+
// `Content-Range: bytes 0-0/12345` carries the total size.
|
|
112
|
+
const contentRange = probe.headers.get("content-range");
|
|
113
|
+
const total = contentRange ? Number(/\/(\d+)$/.exec(contentRange)?.[1]) : Number.NaN;
|
|
114
|
+
if (!Number.isSafeInteger(total) || total < 0) {
|
|
115
|
+
throw new ArchiveError("Remote archive did not report a valid size in Content-Range");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
size: total,
|
|
119
|
+
async read(start, end) {
|
|
120
|
+
assertValidRange(start, end);
|
|
121
|
+
if (start === end) return new Uint8Array(0);
|
|
122
|
+
const response = await doFetch(url, {
|
|
123
|
+
headers: { ...options.headers, range: `bytes=${start}-${end - 1}` },
|
|
124
|
+
});
|
|
125
|
+
if (response.status !== 206) {
|
|
126
|
+
await response.body?.cancel();
|
|
127
|
+
throw new ArchiveError(`Remote archive range request failed (HTTP ${response.status})`);
|
|
128
|
+
}
|
|
129
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
130
|
+
if (bytes.byteLength !== end - start) {
|
|
131
|
+
throw new ArchiveError("Invalid archive: truncated data");
|
|
132
|
+
}
|
|
133
|
+
return bytes;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Options for {@link cachingByteSource}. */
|
|
139
|
+
export interface CachingByteSourceOptions {
|
|
140
|
+
/** Cache block size in bytes. Default 256 KiB. */
|
|
141
|
+
blockSize?: number;
|
|
142
|
+
/** Max cached blocks. Default 64 (16 MiB at the default block size). */
|
|
143
|
+
maxBlocks?: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Wrap a high-latency {@link ByteSource} (HTTP, network filesystems) with an
|
|
148
|
+
* aligned-block LRU cache. Small header reads coalesce into shared block
|
|
149
|
+
* fetches (concurrent readers of one block share a single in-flight request);
|
|
150
|
+
* reads spanning more than two blocks bypass the cache to avoid copying large
|
|
151
|
+
* member payloads through it.
|
|
152
|
+
*/
|
|
153
|
+
export function cachingByteSource(source: ByteSource, options: CachingByteSourceOptions = {}): ByteSource {
|
|
154
|
+
const blockSize = options.blockSize ?? 256 * 1024;
|
|
155
|
+
const blocks = new LRUCache<number, Promise<Uint8Array>>({ max: options.maxBlocks ?? 64 });
|
|
156
|
+
const readBlock = (index: number): Promise<Uint8Array> => {
|
|
157
|
+
const cached = blocks.get(index);
|
|
158
|
+
if (cached) return cached;
|
|
159
|
+
const start = index * blockSize;
|
|
160
|
+
const pending = source.read(start, Math.min(start + blockSize, source.size));
|
|
161
|
+
blocks.set(index, pending);
|
|
162
|
+
pending.catch(() => blocks.delete(index));
|
|
163
|
+
return pending;
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
size: source.size,
|
|
167
|
+
async read(start, end) {
|
|
168
|
+
assertValidRange(start, end);
|
|
169
|
+
if (end > source.size) {
|
|
170
|
+
throw new ArchiveError("Invalid archive: truncated data");
|
|
171
|
+
}
|
|
172
|
+
if (start === end) return new Uint8Array(0);
|
|
173
|
+
const firstBlock = Math.floor(start / blockSize);
|
|
174
|
+
const lastBlock = Math.floor((end - 1) / blockSize);
|
|
175
|
+
if (lastBlock - firstBlock > 1) return source.read(start, end);
|
|
176
|
+
const out = new Uint8Array(end - start);
|
|
177
|
+
for (let index = firstBlock; index <= lastBlock; index++) {
|
|
178
|
+
const block = await readBlock(index);
|
|
179
|
+
const blockStart = index * blockSize;
|
|
180
|
+
const from = Math.max(start, blockStart);
|
|
181
|
+
const to = Math.min(end, blockStart + block.byteLength);
|
|
182
|
+
if (to < Math.min(end, blockStart + blockSize)) {
|
|
183
|
+
throw new ArchiveError("Invalid archive: truncated data");
|
|
184
|
+
}
|
|
185
|
+
out.set(block.subarray(from - blockStart, to - blockStart), from - start);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|