rar-stream 5.1.0 โ†’ 5.1.1

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/README.md CHANGED
@@ -4,33 +4,17 @@
4
4
 
5
5
  [![CI](https://github.com/doom-fish/rar-stream/actions/workflows/ci.yml/badge.svg)](https://github.com/doom-fish/rar-stream/actions/workflows/ci.yml)
6
6
  [![npm version](https://badge.fury.io/js/rar-stream.svg)](https://www.npmjs.com/package/rar-stream)
7
- [![npm downloads](https://img.shields.io/npm/dm/rar-stream.svg)](https://www.npmjs.com/package/rar-stream)
8
7
  [![crates.io](https://img.shields.io/crates/v/rar-stream.svg)](https://crates.io/crates/rar-stream)
9
- [![crates.io downloads](https://img.shields.io/crates/d/rar-stream.svg)](https://crates.io/crates/rar-stream)
10
8
  [![docs.rs](https://docs.rs/rar-stream/badge.svg)](https://docs.rs/rar-stream)
11
- [![MSRV](https://img.shields.io/badge/MSRV-1.85-blue.svg)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)
12
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
10
 
14
- ## What's New in v5.1.0
15
-
16
- - โšก **Parallel RAR5 pipeline**: Beats the official C `unrar` in all 24 benchmark scenarios
17
- - ๐ŸŒ **WASM RAR5 support**: Full RAR5 decompression in the browser via `WasmRar5Decoder`
18
- - ๐Ÿ”Œ **NAPI pipeline**: Node.js users get 40% faster decompression automatically
19
- - ๐Ÿงช **E2E browser tests**: Full Playwright tests for upload โ†’ decompress โ†’ verify workflow
20
-
21
- ## Features
11
+ ## Installation
22
12
 
23
- - ๐Ÿš€ **Fast**: Native Rust implementation with NAPI bindings
24
- - ๐Ÿ“ฆ **Zero dependencies**: Core library has no external dependencies
25
- - ๐ŸŒ **Cross-platform**: Works on Linux, macOS, Windows
26
- - ๐Ÿ”„ **Streaming**: Stream files directly from RAR archives
27
- - ๐Ÿ“š **Multi-volume**: Supports split archives (.rar, .r00, .r01, ...)
28
- - ๐Ÿ—œ๏ธ **Full decompression**: LZSS, PPMd, and filters
29
- - ๐Ÿ” **Encrypted archives**: AES-256/AES-128 decryption for RAR4 & RAR5
30
- - ๐Ÿ†• **RAR4 + RAR5**: Full support for both RAR formats
31
- - ๐ŸŒ **Browser support**: WASM build available
13
+ ### Node.js
32
14
 
33
- ## Installation
15
+ ```bash
16
+ npm install rar-stream
17
+ ```
34
18
 
35
19
  ### Rust
36
20
 
@@ -39,14 +23,10 @@
39
23
  rar-stream = { version = "5", features = ["async", "crypto"] }
40
24
  ```
41
25
 
42
- ### Node.js
26
+ ### Browser (WASM)
43
27
 
44
28
  ```bash
45
- npm install rar-stream
46
- # or
47
- yarn add rar-stream
48
- # or
49
- pnpm add rar-stream
29
+ wasm-pack build --target web --features "wasm,crypto" --no-default-features
50
30
  ```
51
31
 
52
32
  ## Quick Start
@@ -54,254 +34,54 @@ pnpm add rar-stream
54
34
  ```javascript
55
35
  import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
56
36
 
57
- // Open a RAR archive
58
37
  const media = new LocalFileMedia('./archive.rar');
59
38
  const pkg = new RarFilesPackage([media]);
60
-
61
- // Parse and list inner files
62
39
  const files = await pkg.parse();
63
40
 
64
41
  for (const file of files) {
65
42
  console.log(`${file.name}: ${file.length} bytes`);
66
-
67
- // Read entire file into memory
68
43
  const buffer = await file.readToEnd();
69
-
70
- // Or stream (returns Node.js Readable)
71
- const stream = file.createReadStream();
72
- stream.pipe(process.stdout);
73
- }
74
- ```
75
-
76
- ### Rust Quick Start
77
-
78
- ```rust
79
- use rar_stream::{RarFilesPackage, ParseOptions, LocalFileMedia, FileMedia};
80
- use std::sync::Arc;
81
-
82
- #[tokio::main]
83
- async fn main() -> Result<(), Box<dyn std::error::Error>> {
84
- // Open a RAR archive
85
- let file: Arc<dyn FileMedia> = Arc::new(LocalFileMedia::new("archive.rar")?);
86
- let package = RarFilesPackage::new(vec![file]);
87
-
88
- // Parse and list files
89
- let files = package.parse(ParseOptions::default()).await?;
90
- for f in &files {
91
- println!("{}: {} bytes", f.name, f.length);
92
- }
93
-
94
- // Read file content
95
- let content = files[0].read_to_end().await?;
96
- println!("Read {} bytes", content.len());
97
- Ok(())
44
+ // or stream: file.createReadStream().pipe(process.stdout);
98
45
  }
99
46
  ```
100
47
 
101
48
  ## Examples
102
49
 
103
- ### Extract a File to Disk
104
-
105
- ```javascript
106
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
107
- import fs from 'fs';
108
-
109
- const media = new LocalFileMedia('./archive.rar');
110
- const pkg = new RarFilesPackage([media]);
111
- const files = await pkg.parse();
112
-
113
- // Find a specific file
114
- const targetFile = files.find(f => f.name.endsWith('.txt'));
115
- if (targetFile) {
116
- const content = await targetFile.readToEnd();
117
- fs.writeFileSync('./extracted.txt', content);
118
- console.log(`Extracted ${targetFile.name} (${content.length} bytes)`);
119
- }
120
- ```
121
-
122
- ### Stream Video from RAR (Node.js Readable Stream)
123
-
124
- ```javascript
125
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
126
- import fs from 'fs';
127
-
128
- const media = new LocalFileMedia('./movie.rar');
129
- const pkg = new RarFilesPackage([media]);
130
- const files = await pkg.parse();
131
-
132
- const video = files.find(f => f.name.endsWith('.mkv'));
133
- if (video) {
134
- // Get a Node.js Readable stream for the entire file
135
- const stream = video.createReadStream();
136
- stream.pipe(fs.createWriteStream('./extracted-video.mkv'));
137
-
138
- // Or stream a specific byte range (for HTTP range requests)
139
- const rangeStream = video.createReadStream({ start: 0, end: 1024 * 1024 - 1 });
140
- }
141
- ```
142
-
143
- ### WebTorrent Integration
144
-
145
- Use `rar-stream` with WebTorrent to stream video from RAR archives inside torrents:
146
-
147
- ```javascript
148
- import WebTorrent from 'webtorrent';
149
- import { RarFilesPackage } from 'rar-stream';
150
-
151
- const client = new WebTorrent();
152
-
153
- client.add(magnetUri, (torrent) => {
154
- // Find RAR files (includes .rar, .r00, .r01, etc. for multi-volume)
155
- // WebTorrent files already implement the FileMedia interface!
156
- const rarFiles = torrent.files
157
- .filter(f => /\.(rar|r\d{2})$/i.test(f.name))
158
- .sort((a, b) => a.name.localeCompare(b.name));
159
-
160
- // No wrapper needed - pass torrent files directly
161
- const pkg = new RarFilesPackage(rarFiles);
162
- pkg.parse().then(innerFiles => {
163
- const video = innerFiles.find(f => f.name.endsWith('.mkv'));
164
- if (video) {
165
- // Stream video content - this returns a Node.js Readable
166
- const stream = video.createReadStream();
167
-
168
- // Pipe to HTTP response, media player, etc.
169
- stream.pipe(process.stdout);
170
- }
171
- });
172
- });
173
- ```
174
-
175
- ### HTTP Range Request Handler (Express)
176
-
177
- ```javascript
178
- import express from 'express';
179
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
180
-
181
- const app = express();
182
-
183
- // Pre-parse the RAR archive
184
- const media = new LocalFileMedia('./videos.rar');
185
- const pkg = new RarFilesPackage([media]);
186
- const files = await pkg.parse();
187
- const video = files.find(f => f.name.endsWith('.mp4'));
188
-
189
- app.get('/video', (req, res) => {
190
- const range = req.headers.range;
191
- const fileSize = video.length;
192
-
193
- if (range) {
194
- const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
195
- const start = parseInt(startStr, 10);
196
- const end = endStr ? parseInt(endStr, 10) : fileSize - 1;
197
-
198
- res.writeHead(206, {
199
- 'Content-Range': `bytes ${start}-${end}/${fileSize}`,
200
- 'Accept-Ranges': 'bytes',
201
- 'Content-Length': end - start + 1,
202
- 'Content-Type': 'video/mp4',
203
- });
204
-
205
- // Stream the range directly from the RAR archive
206
- const stream = video.createReadStream({ start, end });
207
- stream.pipe(res);
208
- } else {
209
- res.writeHead(200, {
210
- 'Content-Length': fileSize,
211
- 'Content-Type': 'video/mp4',
212
- });
213
- video.createReadStream().pipe(res);
214
- }
215
- });
216
-
217
- app.listen(3000);
218
- ```
219
-
220
- ### Multi-Volume Archives
221
-
222
- ```javascript
223
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
224
- import fs from 'fs';
225
- import path from 'path';
226
-
227
- // Find all volumes in a directory
228
- const dir = './my-archive';
229
- const volumeFiles = fs.readdirSync(dir)
230
- .filter(f => /\.(rar|r\d{2})$/i.test(f))
231
- .sort()
232
- .map(f => new LocalFileMedia(path.join(dir, f)));
233
-
234
- console.log(`Found ${volumeFiles.length} volumes`);
235
-
236
- const pkg = new RarFilesPackage(volumeFiles);
237
- const files = await pkg.parse();
238
-
239
- // Files spanning multiple volumes are handled automatically
240
- for (const file of files) {
241
- console.log(`${file.name}: ${file.length} bytes`);
242
- }
243
- ```
244
-
245
- ### Check if a File is a RAR Archive
246
-
247
- ```javascript
248
- import { isRarArchive, parseRarHeader } from 'rar-stream';
249
- import fs from 'fs';
250
-
251
- // Read first 300 bytes (enough for header detection)
252
- const buffer = Buffer.alloc(300);
253
- const fd = fs.openSync('./unknown-file', 'r');
254
- fs.readSync(fd, buffer, 0, 300, 0);
255
- fs.closeSync(fd);
256
-
257
- if (isRarArchive(buffer)) {
258
- const info = parseRarHeader(buffer);
259
- if (info) {
260
- console.log(`First file: ${info.name}`);
261
- console.log(`Packed size: ${info.packedSize} bytes`);
262
- console.log(`Unpacked size: ${info.unpackedSize} bytes`);
263
- console.log(`Compression method: 0x${info.method.toString(16)}`);
264
- }
265
- } else {
266
- console.log('Not a RAR archive');
267
- }
268
- ```
269
-
270
- ### Limit Number of Files Parsed
50
+ Runnable examples in [`examples/`](./examples):
271
51
 
272
- ```javascript
273
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
52
+ | Example | Run | Description |
53
+ |---------|-----|-------------|
54
+ | [basic.ts](./examples/basic.ts) | `npx tsx examples/basic.ts <path>` | Parse RAR, list files, read content |
55
+ | [http-stream.ts](./examples/http-stream.ts) | `npx tsx examples/http-stream.ts <rar>` | HTTP video server with range requests |
56
+ | [extract.rs](./examples/extract.rs) | `cargo run --release --example extract --features async -- archive.rar out/` | Extract files to disk |
57
+ | [browser.html](./examples/browser.html) | Open in browser (after `npm run build:wasm`) | WASM decompression demo |
58
+ | [profile.rs](./examples/profile.rs) | `cargo run --release --example profile` | Benchmark decompression loop |
59
+ | [benchmark_sizes.rs](./examples/benchmark_sizes.rs) | `cargo run --release --example benchmark_sizes` | Benchmark across file sizes |
274
60
 
275
- const media = new LocalFileMedia('./large-archive.rar');
276
- const pkg = new RarFilesPackage([media]);
277
-
278
- // Only parse first 10 files (useful for previewing large archives)
279
- const files = await pkg.parse({ maxFiles: 10 });
280
- console.log(`Showing first ${files.length} files`);
281
- ```
61
+ ## API
282
62
 
283
- ## API Reference
63
+ ### Classes
284
64
 
285
- ### LocalFileMedia
65
+ | Class | Description |
66
+ |-------|-------------|
67
+ | `LocalFileMedia(path)` | Wraps a local file for reading |
68
+ | `RarFilesPackage(files)` | Parses single or multi-volume RAR archives |
69
+ | `InnerFile` | A file inside the archive |
286
70
 
287
- Represents a local RAR file.
71
+ ### InnerFile
288
72
 
289
73
  ```typescript
290
- class LocalFileMedia {
291
- constructor(path: string);
292
-
293
- readonly name: string; // Filename (basename)
294
- readonly length: number; // File size in bytes
295
-
296
- // Read a byte range into a Buffer
297
- // Create a Readable stream for a byte range
298
- createReadStream(opts: { start: number; end: number }): Readable;
74
+ class InnerFile {
75
+ readonly name: string; // Full path inside archive
76
+ readonly length: number; // Uncompressed size in bytes
77
+ readToEnd(): Promise<Buffer>;
78
+ createReadStream(opts?: { start?: number; end?: number }): Readable;
299
79
  }
300
80
  ```
301
81
 
302
82
  ### FileMedia Interface
303
83
 
304
- Custom data sources (WebTorrent, S3, HTTP, etc.) must implement this interface:
84
+ Custom data sources (WebTorrent, S3, HTTP) must implement:
305
85
 
306
86
  ```typescript
307
87
  interface FileMedia {
@@ -311,134 +91,54 @@ interface FileMedia {
311
91
  }
312
92
  ```
313
93
 
314
- ### RarFilesPackage
315
-
316
- Parses single or multi-volume RAR archives.
317
-
318
- ```typescript
319
- class RarFilesPackage {
320
- constructor(files: FileMedia[]); // LocalFileMedia or custom FileMedia
321
-
322
- parse(opts?: {
323
- maxFiles?: number; // Limit number of files to parse
324
- }): Promise<InnerFile[]>;
325
- }
326
- ```
327
-
328
- ### InnerFile
329
-
330
- Represents a file inside the RAR archive.
331
-
332
- ```typescript
333
- import { Readable } from 'stream';
334
-
335
- class InnerFile {
336
- readonly name: string; // Full path inside archive
337
- readonly length: number; // Uncompressed size in bytes
338
-
339
- // Read entire file into memory
340
- readToEnd(): Promise<Buffer>;
341
-
342
- // Create a Readable stream for the entire file or a byte range
343
- createReadStream(opts?: {
344
- start?: number; // Default: 0
345
- end?: number; // Default: length - 1
346
- }): Readable;
347
- }
348
- ```
349
-
350
94
  ### Utility Functions
351
95
 
352
- ```typescript
353
- // Check if buffer starts with RAR signature
354
- function isRarArchive(buffer: Buffer): boolean;
355
-
356
- // Parse RAR header from buffer (needs ~300 bytes)
357
- function parseRarHeader(buffer: Buffer): RarFileInfo | null;
358
-
359
- // Convert a Readable stream to a Buffer
360
- function streamToBuffer(stream: Readable): Promise<Buffer>;
361
-
362
- // Create a FileMedia from any source with createReadStream
363
- function createFileMedia(source: FileMedia): FileMedia;
364
-
365
- interface RarFileInfo {
366
- name: string;
367
- packedSize: number;
368
- unpackedSize: number;
369
- method: number;
370
- continuesInNext: boolean;
371
- }
372
- ```
373
-
374
- ## Compression Support
375
-
376
- ### RAR Format Compatibility
377
-
378
- | Format | Signature | Support |
379
- |--------|-----------|---------|
380
- | RAR 1.5-4.x (RAR4) | `Rar!\x1a\x07\x00` | โœ… Full |
381
- | RAR 5.0+ (RAR5) | `Rar!\x1a\x07\x01\x00` | โœ… Full |
382
-
383
- ### Compression Methods
384
-
385
- | Method | RAR4 | RAR5 | Description |
386
- |--------|------|------|-------------|
387
- | Store | โœ… | โœ… | No compression |
388
- | LZSS | โœ… | โœ… | Huffman + LZ77 sliding window |
389
- | PPMd | โœ… | โ€” | Context-based (RAR4 only) |
390
-
391
- ### Filter Support
392
-
393
- | Filter | RAR4 | RAR5 | Description |
394
- |--------|------|------|-------------|
395
- | E8 | โœ… | โœ… | x86 CALL preprocessing |
396
- | E8E9 | โœ… | โœ… | x86 CALL/JMP preprocessing |
397
- | Delta | โœ… | โœ… | Byte delta per channel |
398
- | ARM | โ€” | โœ… | ARM branch preprocessing |
399
- | Itanium | โœ… | โ€” | IA-64 preprocessing |
400
- | RGB | โœ… | โ€” | Predictive color filter |
401
- | Audio | โœ… | โ€” | Audio sample predictor |
402
-
403
- ### Encryption Support
404
-
405
- | Feature | RAR4 | RAR5 | Notes |
406
- |---------|------|------|-------|
407
- | Encrypted files | โœ… | โœ… | `crypto` feature |
408
- | Encrypted headers | โ€” | โœ… | RAR5 `-hp` archives |
409
- | Algorithm | AES-128-CBC | AES-256-CBC | โ€” |
410
- | Key derivation | SHA-1 (262k rounds) | PBKDF2-HMAC-SHA256 | โ€” |
411
-
412
- To enable encryption support:
413
-
414
- **Node.js/npm:** Encryption is always available.
415
-
416
- **Rust:**
417
- ```toml
418
- [dependencies]
419
- rar-stream = { version = "5", features = ["async", "crypto"] }
420
- ```
96
+ | Function | Description |
97
+ |----------|-------------|
98
+ | `isRarArchive(buffer)` | Check if buffer starts with RAR signature |
99
+ | `parseRarHeader(buffer)` | Parse RAR4 header from buffer (~300 bytes) |
100
+ | `streamToBuffer(stream)` | Convert Readable to Buffer |
101
+ | `createFileMedia(source)` | Wrap any `{ createReadStream }` as FileMedia |
102
+
103
+ ## Format Support
104
+
105
+ | Feature | RAR4 | RAR5 |
106
+ |---------|------|------|
107
+ | Stored (no compression) | Yes | Yes |
108
+ | LZSS (Huffman + LZ77) | Yes | Yes |
109
+ | PPMd | Yes | -- |
110
+ | E8/E8E9 filters (x86) | Yes | Yes |
111
+ | Delta filter | Yes | Yes |
112
+ | ARM filter | -- | Yes |
113
+ | Itanium/RGB/Audio filters | Yes | -- |
114
+ | Encrypted files (AES) | Yes | Yes |
115
+ | Encrypted headers | -- | Yes |
116
+ | Multi-volume | Yes | Yes |
117
+
118
+ Encryption requires the `crypto` feature (always enabled in npm builds).
421
119
 
422
120
  ## Performance
423
121
 
424
- ### RAR5 Decompression: Faster Than unrar
122
+ Benchmarked against the official C `unrar` (v7.0) using `cargo bench`.
425
123
 
426
- rar-stream's parallel pipeline **beats the official C `unrar`** (v7.0, multi-threaded) across all tested workloads.
124
+ Core decompression (3 KB files, single-threaded):
427
125
 
428
- Benchmark on AMD Ryzen 5 7640HS (6 cores):
126
+ | Method | rar-stream | unrar | Speedup |
127
+ |--------|-----------|-------|---------|
128
+ | LZSS | 8 ยตs | 92 ยตs | 11x |
129
+ | PPMd | 101 ยตs | 177 ยตs | 1.8x |
130
+ | Stored | 54 ns | 123 ยตs | 2170x |
429
131
 
430
- | Archive | Size | rar-stream (pipeline) | unrar | Ratio |
431
- |---------|------|----------------------|-------|-------|
432
- | ISO (binary) | 200 MB | **422ms** | 453ms | **0.93ร—** |
433
- | Text | 200 MB | **144ms** | 202ms | **0.71ร—** |
434
- | Mixed | 200 MB | **342ms** | 527ms | **0.65ร—** |
435
- | Binary | 500 MB | **824ms** | 1149ms | **0.72ร—** |
436
- | Text | 500 MB | **424ms** | 604ms | **0.70ร—** |
437
- | Mixed | 1 GB | **1953ms** | 2550ms | **0.77ร—** |
132
+ With the `parallel` feature (enabled by default in npm), rar-stream's pipeline beats unrar across all 24 benchmark scenarios. Best case: 1.9x faster.
438
133
 
439
- **Wins 24 out of 24 benchmark scenarios** across 6 data types ร— 4 compression settings.
440
-
441
- Best case: **1.9ร— faster** than unrar (ISO 200MB, `-m5 -md128m`).
134
+ | Archive | Size | Pipeline | unrar | Ratio |
135
+ |---------|------|----------|-------|-------|
136
+ | Binary (ISO) | 200 MB | 289ms | 420ms | 0.69x |
137
+ | Text | 200 MB | 119ms | 197ms | 0.61x |
138
+ | Mixed | 200 MB | 307ms | 524ms | 0.59x |
139
+ | Binary | 500 MB | 683ms | 1088ms | 0.63x |
140
+ | Text | 500 MB | 357ms | 590ms | 0.61x |
141
+ | Mixed | 1 GB | 1671ms | 2407ms | 0.69x |
442
142
 
443
143
  <details>
444
144
  <summary>Full benchmark matrix (24 scenarios)</summary>
@@ -446,74 +146,40 @@ Best case: **1.9ร— faster** than unrar (ISO 200MB, `-m5 -md128m`).
446
146
  ```
447
147
  Archive Single Pipeline Unrar Pipe/Unrar
448
148
  ----------------------------------------------------------------
449
- bin-500_m3_32m 1278ms 884ms 1187ms 0.74x
450
- bin-500_m5_128m 1200ms 824ms 1149ms 0.72x
451
- bin-500_m5_32m 1247ms 852ms 1162ms 0.73x
452
- bin-500_m5_4m 1378ms 942ms 1770ms 0.53x
453
- iso-200_m3_32m 715ms 426ms 760ms 0.56x
454
- iso-200_m5_128m 720ms 423ms 811ms 0.52x
455
- iso-200_m5_32m 721ms 422ms 453ms 0.93x
456
- iso-200_m5_4m 717ms 422ms 442ms 0.95x
457
- mixed-1g_m3_32m 2974ms 2109ms 2775ms 0.76x
458
- mixed-1g_m5_128m 3177ms 2213ms 2984ms 0.74x
459
- mixed-1g_m5_32m 2979ms 2086ms 2731ms 0.76x
460
- mixed-1g_m5_4m 2761ms 1953ms 2550ms 0.77x
461
- mixed-200_m3_32m 499ms 385ms 547ms 0.70x
462
- mixed-200_m5_128m 438ms 342ms 527ms 0.65x
463
- mixed-200_m5_32m 495ms 384ms 539ms 0.71x
464
- mixed-200_m5_4m 511ms 395ms 538ms 0.73x
465
- text-200_m3_32m 209ms 145ms 202ms 0.72x
466
- text-200_m5_128m 205ms 144ms 239ms 0.60x
467
- text-200_m5_32m 209ms 144ms 202ms 0.71x
468
- text-200_m5_4m 227ms 153ms 207ms 0.74x
469
- text-500_m3_32m 606ms 432ms 613ms 0.70x
470
- text-500_m5_128m 601ms 431ms 644ms 0.67x
471
- text-500_m5_32m 604ms 424ms 604ms 0.70x
472
- text-500_m5_4m 659ms 455ms 643ms 0.71x
473
- ```
149
+ bin-500_m3_32m 1187ms 736ms 1122ms 0.66x
150
+ bin-500_m5_128m 1122ms 683ms 1088ms 0.63x
151
+ bin-500_m5_32m 1183ms 711ms 1119ms 0.64x
152
+ bin-500_m5_4m 1311ms 765ms 1206ms 0.63x
153
+ iso-200_m3_32m 693ms 289ms 420ms 0.69x
154
+ iso-200_m5_128m 694ms 296ms 455ms 0.65x
155
+ iso-200_m5_32m 697ms 290ms 418ms 0.69x
156
+ iso-200_m5_4m 694ms 293ms 426ms 0.69x
157
+ mixed-1g_m3_32m 2690ms 1818ms 2603ms 0.70x
158
+ mixed-1g_m5_128m 2909ms 1916ms 2852ms 0.67x
159
+ mixed-1g_m5_32m 2699ms 1794ms 2598ms 0.69x
160
+ mixed-1g_m5_4m 2611ms 1671ms 2407ms 0.69x
161
+ mixed-200_m3_32m 465ms 344ms 537ms 0.64x
162
+ mixed-200_m5_128m 413ms 307ms 524ms 0.59x
163
+ mixed-200_m5_32m 463ms 338ms 527ms 0.64x
164
+ mixed-200_m5_4m 487ms 350ms 531ms 0.66x
165
+ text-200_m3_32m 199ms 120ms 200ms 0.60x
166
+ text-200_m5_128m 196ms 119ms 230ms 0.52x
167
+ text-200_m5_32m 197ms 120ms 197ms 0.61x
168
+ text-200_m5_4m 218ms 127ms 199ms 0.64x
169
+ text-500_m3_32m 583ms 362ms 591ms 0.61x
170
+ text-500_m5_128m 572ms 365ms 628ms 0.58x
171
+ text-500_m5_32m 567ms 357ms 590ms 0.61x
172
+ text-500_m5_4m 623ms 382ms 620ms 0.62x
173
+ ```
174
+
175
+ Ratio < 1.0 = rar-stream is faster.
474
176
 
475
177
  </details>
476
178
 
477
- ### Node.js (NAPI) Performance
478
-
479
- | Configuration | Time (200MB) | vs unrar |
480
- |---------------|-------------|----------|
481
- | v5.0.0 (single-threaded) | 1127ms | 2.73ร— slower |
482
- | **v5.1.0 (pipeline)** | **673ms** | **1.53ร— slower** |
483
-
484
- The remaining NAPI gap vs native is I/O + buffer copy overhead, not decompression.
485
-
486
- ### Optimization Features
487
-
488
- - **Parallel pipeline**: Decode + apply in parallel using rayon worker threads
489
- - **Split-buffer decode**: Separates literals from commands for cache-friendly apply
490
- - **LTO (Link-Time Optimization)**: Enabled by default in release builds
491
- - **SIMD**: Automatic vectorization for E8/E9 filter scanning and memcpy
492
- - **Zero-copy streaming**: Direct buffer access without intermediate copies
493
-
494
179
  ## Migrating from v3.x
495
180
 
496
- rar-stream v4 is a complete Rust rewrite with the same API. It's a drop-in replacement:
497
-
498
- ```javascript
499
- // Works the same in v3.x and v4.x
500
- import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
501
-
502
- const media = new LocalFileMedia('./archive.rar');
503
- const pkg = new RarFilesPackage([media]);
504
- const files = await pkg.parse();
505
- ```
506
-
507
- ### Breaking Changes
508
-
509
- - Node.js 18+ required (was 14+)
510
- - Native Rust implementation (faster, lower memory)
181
+ Drop-in replacement. Same API, native Rust implementation. Requires Node.js 18+.
511
182
 
512
183
  ## License
513
184
 
514
185
  MIT
515
-
516
- ## Credits
517
-
518
- - Based on [unrar](https://www.rarlab.com/) reference implementation
519
- - PPMd algorithm by Dmitry Shkarin
package/lib/browser.d.ts CHANGED
@@ -2,20 +2,35 @@
2
2
  * rar-stream browser entry point with Web Streams API support
3
3
  */
4
4
 
5
- // Re-export WASM bindings
5
+ // WASM init
6
+ /**
7
+ * Initialize the WASM module. Safe to call multiple times.
8
+ * Automatically called by all async helper functions.
9
+ */
10
+ export declare function init(wasmUrl?: string | URL | Request | Response | BufferSource | WebAssembly.Module): Promise<void>;
11
+ export declare function initSync(module: { module: BufferSource | WebAssembly.Module } | BufferSource | WebAssembly.Module): void;
12
+
13
+ // Auto-init async helpers (call init() automatically)
14
+ export declare function isRarArchive(data: Uint8Array): Promise<boolean>;
15
+ export declare function getRarVersion(data: Uint8Array): Promise<number>;
16
+ export declare function parseRarHeader(data: Uint8Array): Promise<any>;
17
+ export declare function parseRarHeaders(data: Uint8Array): Promise<any[]>;
18
+ export declare function parseRar5Header(data: Uint8Array): Promise<any>;
19
+ export declare function parseRar5Headers(data: Uint8Array): Promise<any[]>;
20
+
21
+ // Classes (require init() before construction)
22
+ export { WasmRarDecoder, WasmRar5Decoder, WasmRar5Crypto } from '../pkg/rar_stream.d.ts';
23
+ export { WasmRarDecoder as RarDecoder, WasmRar5Decoder as Rar5Decoder } from '../pkg/rar_stream.d.ts';
24
+
25
+ // Direct snake_case access (require init() before use)
6
26
  export {
7
- default as init,
8
- initSync,
9
- isRarArchive,
10
- getRarVersion,
11
- parseRarHeader,
12
- RarDecoder,
13
- WasmRar5Crypto,
14
27
  is_rar_archive,
15
28
  get_rar_version,
16
29
  parse_rar_header,
17
- WasmRarDecoder,
18
- } from '../browser.js';
30
+ parse_rar_headers,
31
+ parse_rar5_header,
32
+ parse_rar5_headers,
33
+ } from '../pkg/rar_stream.d.ts';
19
34
 
20
35
  /** Options for creating a ReadableStream */
21
36
  export interface ReadableStreamOptions {
package/lib/browser.mjs CHANGED
@@ -4,26 +4,91 @@
4
4
  * Provides ReadableStream support for streaming file content in browsers.
5
5
  */
6
6
 
7
- // Re-export all WASM bindings
8
- export {
9
- default as init,
10
- initSync,
11
- is_rar_archive as isRarArchive,
12
- get_rar_version as getRarVersion,
13
- parse_rar_header as parseRarHeader,
14
- WasmRarDecoder as RarDecoder,
15
- } from '../pkg/rar_stream.js';
16
-
17
- // Also export snake_case versions for compatibility
18
- export {
7
+ import wasmInit, {
8
+ initSync as _initSync,
19
9
  is_rar_archive,
20
10
  get_rar_version,
21
11
  parse_rar_header,
12
+ parse_rar_headers,
13
+ parse_rar5_header,
14
+ parse_rar5_headers,
22
15
  WasmRarDecoder,
16
+ WasmRar5Decoder,
17
+ WasmRar5Crypto,
23
18
  } from '../pkg/rar_stream.js';
24
19
 
25
- // Re-export crypto if available
26
- export { WasmRar5Crypto } from '../pkg/rar_stream.js';
20
+ let _initialized = false;
21
+ let _initPromise = null;
22
+
23
+ /**
24
+ * Initialize the WASM module. Safe to call multiple times โ€” subsequent calls are no-ops.
25
+ * Automatically called by all exported functions, but can be called explicitly for eager loading.
26
+ * @param {string | URL | Request | Response | BufferSource | WebAssembly.Module} [wasmUrl] - Optional WASM source
27
+ */
28
+ export async function init(wasmUrl) {
29
+ if (_initialized) return;
30
+ if (_initPromise) return _initPromise;
31
+ _initPromise = wasmInit(wasmUrl).then(() => { _initialized = true; });
32
+ return _initPromise;
33
+ }
34
+
35
+ export { _initSync as initSync };
36
+
37
+ // Auto-init wrappers for all functions
38
+ async function ensureInit() {
39
+ if (!_initialized) await init();
40
+ }
41
+
42
+ /** Check if a buffer contains a RAR signature. */
43
+ export async function isRarArchive(data) {
44
+ await ensureInit();
45
+ return is_rar_archive(data);
46
+ }
47
+
48
+ /** Get the RAR format version (15 for RAR4, 50 for RAR5, 0 if not RAR). */
49
+ export async function getRarVersion(data) {
50
+ await ensureInit();
51
+ return get_rar_version(data);
52
+ }
53
+
54
+ /** Parse the first RAR4 file header from a buffer. */
55
+ export async function parseRarHeader(data) {
56
+ await ensureInit();
57
+ return parse_rar_header(data);
58
+ }
59
+
60
+ /** Parse all RAR4 file headers from a buffer. */
61
+ export async function parseRarHeaders(data) {
62
+ await ensureInit();
63
+ return parse_rar_headers(data);
64
+ }
65
+
66
+ /** Parse the first RAR5 file header from a buffer. */
67
+ export async function parseRar5Header(data) {
68
+ await ensureInit();
69
+ return parse_rar5_header(data);
70
+ }
71
+
72
+ /** Parse all RAR5 file headers from a buffer. */
73
+ export async function parseRar5Headers(data) {
74
+ await ensureInit();
75
+ return parse_rar5_headers(data);
76
+ }
77
+
78
+ // Re-export classes (require manual init() before use)
79
+ export { WasmRarDecoder, WasmRar5Decoder, WasmRar5Crypto };
80
+ // Aliases
81
+ export { WasmRarDecoder as RarDecoder, WasmRar5Decoder as Rar5Decoder };
82
+
83
+ // Re-export snake_case direct access (require manual init() before use)
84
+ export {
85
+ is_rar_archive,
86
+ get_rar_version,
87
+ parse_rar_header,
88
+ parse_rar_headers,
89
+ parse_rar5_header,
90
+ parse_rar5_headers,
91
+ };
27
92
 
28
93
  /**
29
94
  * Create a Web ReadableStream from an async data source.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rar-stream",
3
- "version": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "description": "RAR streaming library - Rust implementation with NAPI and WASM bindings",
5
5
  "main": "lib/index.mjs",
6
6
  "browser": "lib/browser.mjs",
@@ -105,12 +105,12 @@
105
105
  "*.node"
106
106
  ],
107
107
  "optionalDependencies": {
108
- "rar-stream-win32-x64-msvc": "5.1.0",
109
- "rar-stream-darwin-x64": "5.1.0",
110
- "rar-stream-linux-x64-gnu": "5.1.0",
111
- "rar-stream-linux-x64-musl": "5.1.0",
112
- "rar-stream-linux-arm64-gnu": "5.1.0",
113
- "rar-stream-darwin-arm64": "5.1.0",
114
- "rar-stream-linux-arm64-musl": "5.1.0"
108
+ "rar-stream-win32-x64-msvc": "5.1.1",
109
+ "rar-stream-darwin-x64": "5.1.1",
110
+ "rar-stream-linux-x64-gnu": "5.1.1",
111
+ "rar-stream-linux-x64-musl": "5.1.1",
112
+ "rar-stream-linux-arm64-gnu": "5.1.1",
113
+ "rar-stream-darwin-arm64": "5.1.1",
114
+ "rar-stream-linux-arm64-musl": "5.1.1"
115
115
  }
116
116
  }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file