streaming 7-Zip archive extractor
0

Configure Feed

Select the types of activity you want to include in your feed.

chore: tighten JSDoc on public surface

trim implementation narration from `SevenZipEntry`, `unsevenzip`, error
classes, and `concatReaders` where the prose was leaking internals callers
don't need. also fix a few inaccuracies: `unsevenzip` documented its
parameter under the wrong name and omitted `options`, the module preamble
claimed single-volume only, and `ChecksumMismatchError` mentioned size
mismatches it never throws.

+23 -36
+3 -6
lib/errors.ts
··· 1 1 /** 2 2 * @module 3 3 * 4 - * the error taxonomy thrown by the {@link unsevenzip} entry point. every error 5 - * is an instance of {@link SevenZipError}, so a caller can branch with 6 - * `instanceof` for the cases it knows how to recover from (re-prompt on 7 - * {@link InvalidPasswordError}, prompt for one on {@link MissingPasswordError}) 8 - * and fall through to a generic `SevenZipError` for the rest. 4 + * errors thrown by the 7z library. every error is an instance of 5 + * {@link SevenZipError}, so callers can branch with `instanceof`. 9 6 */ 10 7 11 8 /** base class for every error thrown by the 7z library. */ ··· 13 10 override name: string = 'SevenZipError'; 14 11 } 15 12 16 - /** stored data did not match a checksum or size recorded in the archive. */ 13 + /** stored data did not match a CRC32 recorded in the archive. */ 17 14 export class ChecksumMismatchError extends SevenZipError { 18 15 override name: string = 'ChecksumMismatchError'; 19 16 }
+13 -20
lib/extract.ts
··· 28 28 29 29 // #region folder cache 30 30 31 - /** 32 - * lazily decodes a single folder and caches its concatenated output so 33 - * entries that share the folder (i.e. live in the same solid block) 34 - * pay the decode cost once. 35 - */ 31 + // caches the decoded output of each folder so entries that share a folder 32 + // (i.e. live in the same solid block) pay the decode cost once. 36 33 class FolderCache { 37 34 readonly #reader: Reader; 38 35 readonly #header: ArchiveHeader; ··· 136 133 // #region entry 137 134 138 135 /** 139 - * a single file entry within a 7z archive. metadata fields are populated 140 - * up front; content is decoded lazily when {@link body}, {@link bytes}, 141 - * or one of the convenience readers is called. 136 + * a single file entry within a 7z archive. metadata is available immediately; 137 + * content is decoded on demand when {@link body}, {@link bytes}, or one of the 138 + * convenience readers is called. 142 139 */ 143 140 export class SevenZipEntry { 144 141 readonly #file: FileEntry; ··· 169 166 return this.#file.size; 170 167 } 171 168 172 - /** whether the entry is a directory (has no data stream). */ 169 + /** whether the entry is a directory. */ 173 170 get isDirectory(): boolean { 174 171 return this.#file.isDirectory; 175 172 } ··· 188 185 return this.#file.crc; 189 186 } 190 187 191 - /** 192 - * the last-modification time as milliseconds since the unix epoch, or 193 - * `NaN` when no mtime was stored. 7z stores NT timestamps internally; 194 - * this getter converts to JS-friendly ms. 195 - */ 188 + /** the modification time in unix-epoch ms, or `NaN` when unset. */ 196 189 get mtime(): number { 197 190 return ntTimeToMillis(this.#file.mTime); 198 191 } ··· 214 207 215 208 /** 216 209 * a readable stream of the entry's plaintext content. each call returns 217 - * a fresh stream; the folder's decoded bytes are cached internally so 218 - * repeat access only pays the slice cost, not the decode cost. 210 + * a fresh stream. 219 211 */ 220 212 get body(): ReadableStream<Uint8Array> { 221 213 const size = this.#file.size; ··· 315 307 // #region unsevenzip 316 308 317 309 /** 318 - * extracts entries from a 7z archive. yielded entries can be read on 319 - * demand; entries within the same solid block share a folder-level 320 - * decode cache. 310 + * extracts entries from a 7z archive. yielded entries can be read on demand. 321 311 * 322 - * @param reader random-access archive source 312 + * @param source a single reader, or an ordered list of readers for a 313 + * multi-volume archive 314 + * @param options optional extraction settings, including the decryption 315 + * password 323 316 * @returns async generator yielding the archive's file entries 324 317 */ 325 318 export async function* unsevenzip(
+2 -1
lib/mod.ts
··· 5 5 * 6 6 * archive contents are read through a {@link Reader}; the `./deno` and `./node` 7 7 * entry points provide readers backed by the respective runtime's file APIs. 8 - * use {@link unsevenzip} to walk a single-volume archive's entries. 8 + * use {@link unsevenzip} to walk an archive's entries; pass an ordered list of 9 + * readers to span a multi-volume archive. 9 10 */ 10 11 11 12 export { SevenZipEntry, unsevenzip, type UnsevenzipOptions } from './extract.ts';
+5 -9
lib/reader/concat.ts
··· 10 10 import type { Reader } from './types.ts'; 11 11 12 12 /** 13 - * combines an ordered list of readers into a single virtual reader. 14 - * 15 - * a read that crosses a volume boundary stitches together byte ranges 16 - * from however many underlying readers it spans, in order. the result 17 - * stream emits chunks from each underlying reader back-to-back, with no 18 - * additional buffering. 13 + * combines an ordered list of readers into a single virtual reader; reads that 14 + * cross a volume boundary span the underlying readers in order. 19 15 * 20 - * @param readers ordered readers; the first holds the signature header, 21 - * the last holds the NextHeader 22 - * @returns a Reader that exposes the concatenated byte sequence 16 + * @param readers ordered readers; the first holds the signature header, the 17 + * last holds the NextHeader 18 + * @returns a reader exposing the concatenated byte sequence 23 19 */ 24 20 export function concatReaders(readers: readonly Reader[]): Reader { 25 21 if (readers.length === 0) {