7zip-native 0.0.1 → 0.0.6
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 +255 -59
- package/dist/index.cjs +201 -40
- package/dist/index.d.cts +20 -7
- package/dist/index.d.mts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.mjs +202 -41
- package/package.json +3 -1
- package/prebuilds/darwin-arm64/7zip_native.node +0 -0
- package/prebuilds/darwin-arm64/lib7z.dylib +0 -0
- package/prebuilds/darwin-x64/7zip_native.node +0 -0
- package/prebuilds/darwin-x64/lib7z.dylib +0 -0
- package/prebuilds/linux-arm/7z.so +0 -0
- package/prebuilds/linux-arm/7zip_native.node +0 -0
- package/prebuilds/linux-arm64/7z.so +0 -0
- package/prebuilds/linux-arm64/7zip_native.node +0 -0
- package/prebuilds/linux-ia32/7z.so +0 -0
- package/prebuilds/linux-ia32/7zip_native.node +0 -0
- package/prebuilds/win32-arm64/7z.dll +0 -0
- package/prebuilds/win32-arm64/7zip_native.node +0 -0
- package/prebuilds/win32-x64/7z.dll +0 -0
- package/prebuilds/win32-x64/7zip_native.node +0 -0
- package/scripts/build-native.mjs +119 -0
- package/scripts/clean-upstream-build.mjs +9 -0
- package/scripts/verify-prebuild.mjs +29 -0
package/README.md
CHANGED
|
@@ -3,102 +3,298 @@
|
|
|
3
3
|
|
|
4
4
|
# 7zip-native
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
### Current version `26.05`
|
|
7
|
+
Upstream 7-Zip archive engine from https://github.com/ip7z/7zip/
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Native Node-API bindings for the 7-Zip archive engine. Use a typed, Promise-based Node.js API to inspect archives, read entry data, stream large files, or extract entries without launching `7z`, `7zz`, or another archive executable.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
The package includes the native addon and the matching 7-Zip engine for each supported target. Compression and decompression are provided by the upstream 7-Zip implementation.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
11
14
|
|
|
12
15
|
```sh
|
|
13
|
-
|
|
14
|
-
cd 7zip-native
|
|
15
|
-
npm ci
|
|
16
|
-
npm run build
|
|
17
|
-
npm test
|
|
16
|
+
npm install 7zip-native
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
The package requires Node.js 18 or newer.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
```ts
|
|
24
|
+
import {Archive} from '7zip-native';
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
const archive = await Archive.open('./comics.cbz');
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
try
|
|
29
|
+
{
|
|
30
|
+
const entries = await archive.entries();
|
|
31
|
+
const cover = await archive.getEntry('chapter/cover.jpg');
|
|
32
|
+
|
|
33
|
+
if(cover && !cover.isDirectory)
|
|
34
|
+
{
|
|
35
|
+
const preview = await archive.read(cover.path, {offset: 0, length: 64 * 1024});
|
|
36
|
+
const image = await archive.extract(cover.path);
|
|
37
|
+
console.log(entries.length, preview.length, image.length);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
finally
|
|
41
|
+
{
|
|
42
|
+
await archive.close();
|
|
43
|
+
}
|
|
30
44
|
```
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
The package supports both ES modules and CommonJS:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// ES module
|
|
50
|
+
import {Archive} from '7zip-native';
|
|
51
|
+
|
|
52
|
+
// CommonJS
|
|
53
|
+
const {Archive} = require('7zip-native');
|
|
54
|
+
```
|
|
33
55
|
|
|
34
56
|
## API
|
|
35
57
|
|
|
58
|
+
### `Archive.open(path, options?)`
|
|
59
|
+
|
|
60
|
+
Opens an archive from a filesystem path and indexes its metadata asynchronously.
|
|
61
|
+
|
|
36
62
|
```ts
|
|
37
|
-
|
|
63
|
+
const archive = await Archive.open('./archive.7z', {password: 'secret'});
|
|
64
|
+
```
|
|
38
65
|
|
|
39
|
-
|
|
66
|
+
`options.password` is passed to the upstream handler when it requests a password. It is held for the lifetime of the archive handle, is not logged, and is limited to 1 MiB of UTF-8 data.
|
|
67
|
+
|
|
68
|
+
### Listing and looking up entries
|
|
69
|
+
|
|
70
|
+
```ts
|
|
40
71
|
const entries = await archive.entries();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
const byPath = await archive.getEntry('folder/page.jpg');
|
|
73
|
+
const byId = await archive.getEntry(0);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`entries()` returns cached metadata. Entry IDs are zero-based and stable for the lifetime of the opened archive. `getEntry()` returns `undefined` if the ID or full path does not match.
|
|
77
|
+
|
|
78
|
+
All entry-targeted methods accept either the entry's numeric `id` or its exact archive-internal `path`: `extract()`, `read()`, `createReadStream()`, and `extractToStream()`. Batch `extract()` calls can mix IDs and paths. For folder extraction, use either the `ids` or `paths` option; omit both to extract every entry.
|
|
79
|
+
|
|
80
|
+
Each entry includes:
|
|
81
|
+
|
|
82
|
+
| Property | Type | Description |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `id` | `number` | Stable ID within this opened archive |
|
|
85
|
+
| `path` | `string` | Entry path, using `/` separators |
|
|
86
|
+
| `size` | `bigint` | Uncompressed size |
|
|
87
|
+
| `compressedSize` | `bigint` | Compressed size |
|
|
88
|
+
| `isDirectory` | `boolean` | Whether the entry is a directory |
|
|
89
|
+
| `encrypted` | `boolean` | Whether the entry is encrypted |
|
|
90
|
+
| `attributes` | `number` | Format-specific attributes |
|
|
91
|
+
| `crc` | `number \| undefined` | CRC when supplied by the format |
|
|
92
|
+
|
|
93
|
+
### Extracting to memory
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const image = await archive.extract(entry.id); // Buffer
|
|
97
|
+
const sameImage = await archive.extract(entry.path); // Buffer
|
|
98
|
+
const pages = await archive.extract([page1.id, page2.path]); // Buffer[]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Paths use `/` separators. Batch results are returned in the same order as the requested selectors; duplicate entries are rejected. A missing path rejects with `ERR_INVALID_ENTRY`. A buffered extraction is limited to 256 MiB per entry, with a 512 MiB cap on native results pending transfer to JavaScript. For large entries, use a stream or extract to a folder instead.
|
|
102
|
+
|
|
103
|
+
### Reading a byte range
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const header = await archive.read(entry.path, {
|
|
107
|
+
offset: 0,
|
|
108
|
+
length: 64 * 1024,
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Offsets and lengths accept safe integers or `bigint`. A range read returns only the requested bytes and is limited to 64 MiB. It uses the handler's normal extraction path, however, so a sequential or solid format may still need to decompress preceding data or the whole entry. It is not guaranteed to be constant-time random access.
|
|
113
|
+
|
|
114
|
+
### Streams and output destinations
|
|
115
|
+
|
|
116
|
+
Create a standard Node.js `Readable` for a large entry:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
for await (const chunk of archive.createReadStream(entry.path, {
|
|
120
|
+
highWaterMark: 256 * 1024,
|
|
121
|
+
signal: abortController.signal,
|
|
122
|
+
}))
|
|
123
|
+
{
|
|
124
|
+
// Consume each Buffer chunk before requesting more data.
|
|
45
125
|
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The native engine emits bounded 64 KiB chunks and waits for stream demand, preserving backpressure. Destroying the stream or aborting its signal cancels extraction at the next output boundary. Streams default to a 4 GiB output limit; set `maxBytes` to another non-negative `number` or `bigint`, or to `null` to disable the limit.
|
|
129
|
+
|
|
130
|
+
To pipe directly to a Node.js `Writable`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import {createWriteStream} from 'node:fs';
|
|
134
|
+
|
|
135
|
+
await archive.extractToStream(entry.path, createWriteStream('./page.jpg'), {
|
|
136
|
+
signal: abortController.signal,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This respects backpressure and ends the destination when extraction succeeds. A destination error, early close, or abort rejects the Promise.
|
|
141
|
+
|
|
142
|
+
### Extracting to a folder
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
await archive.extractToFolder('./unpacked', {
|
|
146
|
+
paths: ['chapter/page1.jpg', 'chapter/page2.jpg'], // alternatively, use ids
|
|
147
|
+
overwrite: true,
|
|
148
|
+
onProgress: ({filesExtracted, totalFiles, bytesProcessed, totalBytes, percent}) => {
|
|
149
|
+
console.log(`${filesExtracted}/${totalFiles} files, ${percent}% (${bytesProcessed}/${totalBytes} bytes)`);
|
|
150
|
+
},
|
|
151
|
+
maxBytes: 2n * 1024n * 1024n * 1024n,
|
|
152
|
+
signal: abortController.signal,
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Folder extraction accepts either `ids` or exact `paths`; omit both to extract every entry, and do not provide both together. Paths use archive-internal `/` separators. Extraction is sequential and uses bounded memory. Its default total output limit is 4 GiB; `maxBytes: null` disables that limit. Set `onProgress` to receive the number of regular files selected and fully extracted, the bytes emitted so far, the total uncompressed bytes, and the calculated percentage. It is called before extraction, as data chunks are processed, and after each file completes. By default, existing files are not overwritten; set `overwrite: true` to replace existing regular files. Replacement is committed only after that entry has been fully extracted, using a temporary file alongside the destination so a failure does not truncate the existing file. This can temporarily require extra disk space up to the size of the entry being replaced. Symlink or non-file targets are rejected. Unsafe archive paths, path collisions, and symlinked destination components are also rejected. Archive links are written as regular file data; the API does not create symbolic or hard links. If extraction fails partway through, files already written (or partially written) may remain in the destination.
|
|
157
|
+
|
|
158
|
+
### Concurrency
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const current = Archive.concurrency();
|
|
162
|
+
Archive.concurrency(8);
|
|
163
|
+
Archive.concurrency(0); // reset to the default
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The setting controls the process-wide native worker pool used by extraction, streams, and range reads. The default is the lesser of 4 and the parallelism available to Node.js; the allowed range is 1–128 workers, with `0` restoring the default.
|
|
167
|
+
|
|
168
|
+
Concurrent operations on one archive use independent decoder contexts rather than calling one 7-Zip archive object simultaneously. This is safe for handlers that support independent contexts, but it does not guarantee a speedup. Solid archives may decompress shared data more than once; batching related entries may be faster.
|
|
169
|
+
|
|
170
|
+
### Closing an archive
|
|
171
|
+
|
|
172
|
+
Call `close()` when finished. It rejects future API calls; operations already submitted keep the native state alive until they finish.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
46
175
|
await archive.close();
|
|
47
176
|
```
|
|
48
177
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
178
|
+
## Errors and limits
|
|
179
|
+
|
|
180
|
+
Invalid argument types and values throw standard `TypeError` or `RangeError`. Archive and extraction failures use `ArchiveError`, which has a stable `code` property:
|
|
181
|
+
|
|
182
|
+
| Code | Meaning |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `ERR_ARCHIVE_CLOSED` | Operation attempted after `close()` |
|
|
185
|
+
| `ERR_INVALID_ENTRY` | Entry ID is invalid or out of range |
|
|
186
|
+
| `ERR_INVALID_RANGE` | Byte range is invalid or exceeds the native offset limit |
|
|
187
|
+
| `ERR_RESOURCE_LIMIT` | Configured or default output limit was exceeded |
|
|
188
|
+
| `ERR_ARCHIVE_OPEN` | Archive could not be opened or indexed |
|
|
189
|
+
| `ERR_ARCHIVE_PASSWORD` | Password is missing or incorrect when requested by the handler |
|
|
190
|
+
| `ERR_ARCHIVE_PATH` | Unsafe or colliding path during folder extraction |
|
|
191
|
+
| `ERR_ARCHIVE_EXTRACT` | Entry extraction failed |
|
|
192
|
+
|
|
193
|
+
Buffered `extract()` and `read()` do not currently support cancellation. Streams and folder extraction accept an `AbortSignal`.
|
|
194
|
+
|
|
195
|
+
## Supported formats
|
|
196
|
+
|
|
197
|
+
The package uses the handlers registered in the pinned upstream `Format7zF` bundle, including 7z, ZIP, RAR, TAR, GZIP, BZIP2, XZ, WIM, ISO/UDF, CAB, ARJ, LZH, CPIO, Zstandard, and additional formats enabled by that upstream revision. Whether a particular archive can be extracted depends on its handler and compression method; unsupported methods, corrupt data, and integrity failures reject the operation. Standalone compression codecs are not necessarily archive formats.
|
|
198
|
+
|
|
199
|
+
### Format-specific notes
|
|
62
200
|
|
|
63
|
-
|
|
201
|
+
- **RAR:** extraction uses upstream code subject to its LGPL terms and the unRAR restriction. This package does not create RAR archives or implement the proprietary RAR compression algorithm. Review the upstream notices before redistribution.
|
|
202
|
+
- **ACE:** not supported by this 7-Zip revision.
|
|
203
|
+
- **Encryption:** password callbacks are connected to archive opening and extraction. AES-256 encrypted ZIP extraction and range reads are tested on Linux x64; encrypted-header behavior and password variants in other formats are not yet verified.
|
|
204
|
+
- **Partial reads:** memory use is limited to the requested range, but the decoder may still process more data internally, especially for solid or sequential formats.
|
|
64
205
|
|
|
65
|
-
|
|
206
|
+
See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for third-party redistribution terms.
|
|
66
207
|
|
|
67
|
-
|
|
208
|
+
## Platform support
|
|
68
209
|
|
|
69
|
-
|
|
70
|
-
- ACE is not supplied by this 7-Zip revision and is not supported.
|
|
71
|
-
- The upstream 7z handler is covered by a native open-and-extract integration test on Linux x64. Support for individual compression methods and encrypted variants still depends on the capabilities of the upstream handler.
|
|
72
|
-
- Password callbacks are wired for upstream archive opening and extraction. AES-256 encrypted ZIP entry extraction and ranged reads are tested on Linux x64. Wrong or missing passwords requested by a handler are reported with `ArchiveError.code === 'ERR_ARCHIVE_PASSWORD'`. Encrypted-header behavior and password variants in other formats are not yet verified.
|
|
73
|
-
- Partial reads bound memory to the selected range but use the normal `IInArchive::Extract` path. Sequential or solid decoders may decompress preceding data or the whole entry; this is not constant-time random access and does not promise early decoder termination.
|
|
74
|
-
- Opening and metadata queries use Node's libuv workers. Extraction and ranged reads use a persistent native worker pool, defaulting to the lesser of 4 and the processors available to Node. `Archive.concurrency(n)` sets the process-wide pool size; submitted native operations remain bounded.
|
|
75
|
-
- The addon never calls one `IInArchive` instance concurrently. It retains independent archive and input-stream contexts per handle and lazily creates more as concurrent work needs them. Context creation checks the archive's size and modification time; reopen the archive after changing it.
|
|
76
|
-
- Concurrent extraction from a solid archive is safe with independent contexts but may repeat decompression of shared solid blocks. There is no decompressed-block cache, so batching related entries can be faster.
|
|
77
|
-
- Cancellation of buffered `extract()`/`read()` requests and decompressed-block caching are not implemented. Streaming and folder extraction have bounded defaults that can be adjusted per operation; buffered methods retain their fixed in-memory limits.
|
|
78
|
-
- Cross-platform builds and runtime behavior are not claimed until the corresponding matrix jobs succeed. No performance claim is made without measurements on the target platform.
|
|
210
|
+
The package includes prebuilt binaries for:
|
|
79
211
|
|
|
80
|
-
|
|
212
|
+
| Platform | Architectures |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| Linux | x64, x86 (ia32), ARM32, ARM64 |
|
|
215
|
+
| macOS | x64, ARM64 |
|
|
216
|
+
| Windows | x64, ARM64 |
|
|
81
217
|
|
|
82
|
-
|
|
218
|
+
Windows x86 (ia32) is not supported by the published prebuilt binaries.
|
|
83
219
|
|
|
84
|
-
|
|
220
|
+
## Electron
|
|
221
|
+
|
|
222
|
+
The addon uses Node-API, but the addon and 7-Zip engine still need to match the Electron application's operating system and architecture. Use a matching package prebuild when available, or rebuild for the Electron version and target architecture using your Electron build tooling.
|
|
223
|
+
|
|
224
|
+
When packaging with ASAR, ensure both `7zip_native.node` and the matching engine library (`7z.dll`, `7z.so`, or `lib7z.dylib`) are physically unpacked and that the runtime paths resolve to those unpacked files. For `electron-builder`, an `asarUnpack` configuration can include:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"build": {
|
|
229
|
+
"asarUnpack": [
|
|
230
|
+
"node_modules/7zip-native/prebuilds/**/*.node",
|
|
231
|
+
"node_modules/7zip-native/prebuilds/**/*.dll",
|
|
232
|
+
"node_modules/7zip-native/prebuilds/**/*.so",
|
|
233
|
+
"node_modules/7zip-native/prebuilds/**/*.dylib"
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Test the packaged application on each target; ASAR unpacking does not replace checking that the addon and engine can both be loaded from the packaged runtime.
|
|
240
|
+
|
|
241
|
+
## Build from source
|
|
85
242
|
|
|
86
243
|
```sh
|
|
87
|
-
|
|
244
|
+
git clone --recurse-submodules https://github.com/ollm/7zip-native.git
|
|
245
|
+
cd 7zip-native
|
|
246
|
+
npm ci --ignore-scripts
|
|
247
|
+
npm run build
|
|
248
|
+
npm test
|
|
88
249
|
```
|
|
89
250
|
|
|
90
|
-
|
|
251
|
+
Source builds require a C++17 toolchain, Python 3, Node.js headers (downloaded by `node-gyp`), and the upstream submodule. Linux and macOS use GNU make; Windows requires Visual C++ build tools and `nmake`. `npm run build:native` compiles the upstream `Format7zF` bundle and Node-API addon, then stages them under `prebuilds/<platform>-<arch>/`.
|
|
91
252
|
|
|
92
|
-
|
|
253
|
+
To update the upstream submodule deliberately:
|
|
93
254
|
|
|
94
255
|
```sh
|
|
95
|
-
|
|
256
|
+
git submodule update --remote vendor/7zip
|
|
257
|
+
git -C vendor/7zip rev-parse HEAD
|
|
258
|
+
git add .gitmodules vendor/7zip
|
|
96
259
|
```
|
|
97
260
|
|
|
98
|
-
|
|
261
|
+
Review the upstream changes and commit the new submodule revision. The current submodule is pinned to 7-Zip 26.05 (`b88c6bc09339de32f7d18bbab900686b244f42ba`).
|
|
99
262
|
|
|
100
|
-
##
|
|
263
|
+
## Benchmarks
|
|
264
|
+
|
|
265
|
+
Run the native benchmark against a representative archive:
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
BENCH_ITERATIONS=20 npm run benchmark -- "./archive.zip"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
It measures native open/list, prefix reads, full extraction, and reads across independent archive handles. `BENCH_ITERATIONS` defaults to 15 and must be at least 3.
|
|
272
|
+
|
|
273
|
+
Compare the native API with the `7zip-bin-full` command-line baseline:
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
npm run benchmark:compare -- \
|
|
277
|
+
--archive "./archive.zip" \
|
|
278
|
+
--entry "chapter/page.jpg" \
|
|
279
|
+
--iterations 20 \
|
|
280
|
+
--read-bytes 65536 \
|
|
281
|
+
--concurrency 4
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The comparison validates entry listings and output data before reporting median and p95 latency. CLI timings include process startup. Since the CLI has no ranged-read API, its range baseline extracts the entry to stdout and slices the requested bytes; this is not equivalent to native range reading. Captured output is capped at 256 MiB, and full-archive comparison is skipped if an entry exceeds that limit.
|
|
285
|
+
|
|
286
|
+
Compare extraction concurrency:
|
|
287
|
+
|
|
288
|
+
```sh
|
|
289
|
+
npm run benchmark:concurrency -- \
|
|
290
|
+
--archive "./archive.7z" \
|
|
291
|
+
--iterations 20 \
|
|
292
|
+
--concurrencies 1,2,3,4,5,8,16 \
|
|
293
|
+
--max-files 20
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The report includes median and p95 latency, throughput, speedup, and the number of entries that can overlap. `--max-files` selects evenly spaced entries, which is useful when sampling solid archives. Benchmarks report measurements only; results depend on the archive, machine, and workload and do not guarantee a speedup.
|
|
101
297
|
|
|
102
|
-
|
|
298
|
+
## License
|
|
103
299
|
|
|
104
|
-
|
|
300
|
+
This package is MIT-licensed. The integrated 7-Zip code and other bundled dependencies have their own licensing terms; see [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) before redistribution.
|