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 +98 -432
- package/lib/browser.d.ts +25 -10
- package/lib/browser.mjs +79 -14
- package/package.json +8 -8
- package/rar-stream.darwin-arm64.node +0 -0
- package/rar-stream.darwin-x64.node +0 -0
- package/rar-stream.linux-arm64-gnu.node +0 -0
- package/rar-stream.linux-x64-gnu.node +0 -0
- package/rar-stream.linux-x64-musl.node +0 -0
- package/rar-stream.win32-x64-msvc.node +0 -0
package/README.md
CHANGED
|
@@ -4,33 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/doom-fish/rar-stream/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/rar-stream)
|
|
7
|
-
[](https://www.npmjs.com/package/rar-stream)
|
|
8
7
|
[](https://crates.io/crates/rar-stream)
|
|
9
|
-
[](https://crates.io/crates/rar-stream)
|
|
10
8
|
[](https://docs.rs/rar-stream)
|
|
11
|
-
[](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)
|
|
12
9
|
[](https://opensource.org/licenses/MIT)
|
|
13
10
|
|
|
14
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
26
|
+
### Browser (WASM)
|
|
43
27
|
|
|
44
28
|
```bash
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
### Classes
|
|
284
64
|
|
|
285
|
-
|
|
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
|
-
|
|
71
|
+
### InnerFile
|
|
288
72
|
|
|
289
73
|
```typescript
|
|
290
|
-
class
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
122
|
+
Benchmarked against the official C `unrar` (v7.0) using `cargo bench`.
|
|
425
123
|
|
|
426
|
-
|
|
124
|
+
Core decompression (3 KB files, single-threaded):
|
|
427
125
|
|
|
428
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
450
|
-
bin-500_m5_128m
|
|
451
|
-
bin-500_m5_32m
|
|
452
|
-
bin-500_m5_4m
|
|
453
|
-
iso-200_m3_32m
|
|
454
|
-
iso-200_m5_128m
|
|
455
|
-
iso-200_m5_32m
|
|
456
|
-
iso-200_m5_4m
|
|
457
|
-
mixed-1g_m3_32m
|
|
458
|
-
mixed-1g_m5_128m
|
|
459
|
-
mixed-1g_m5_32m
|
|
460
|
-
mixed-1g_m5_4m
|
|
461
|
-
mixed-200_m3_32m
|
|
462
|
-
mixed-200_m5_128m
|
|
463
|
-
mixed-200_m5_32m
|
|
464
|
-
mixed-200_m5_4m
|
|
465
|
-
text-200_m3_32m
|
|
466
|
-
text-200_m5_128m
|
|
467
|
-
text-200_m5_32m
|
|
468
|
-
text-200_m5_4m
|
|
469
|
-
text-500_m3_32m
|
|
470
|
-
text-500_m5_128m
|
|
471
|
-
text-500_m5_32m
|
|
472
|
-
text-500_m5_4m
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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.
|
|
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.
|
|
109
|
-
"rar-stream-darwin-x64": "5.1.
|
|
110
|
-
"rar-stream-linux-x64-gnu": "5.1.
|
|
111
|
-
"rar-stream-linux-x64-musl": "5.1.
|
|
112
|
-
"rar-stream-linux-arm64-gnu": "5.1.
|
|
113
|
-
"rar-stream-darwin-arm64": "5.1.
|
|
114
|
-
"rar-stream-linux-arm64-musl": "5.1.
|
|
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
|