7zip-native 0.0.8 → 0.0.12
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 +46 -14
- package/bench/compare.mts +25 -11
- package/bench/folder-concurrency.mts +5 -12
- package/bench/multiprocess-extract.cjs +334 -0
- package/dist/index.cjs +215 -110
- package/dist/index.d.cts +10 -9
- package/dist/index.d.mts +10 -9
- package/dist/index.d.ts +10 -9
- package/dist/index.mjs +217 -112
- package/native/addon.cpp +1399 -71
- package/package.json +4 -2
- package/patches/deflate-input-buffer-size.patch +47 -0
- 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/linux-x64/7z.so +0 -0
- package/prebuilds/linux-x64/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 +22 -1
- package/vendor/7zip/CPP/7zip/Archive/Archive2.def +1 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/7z.so +0 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/Alloc.o +0 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/DeflateDecoder.o +0 -0
- package/vendor/7zip/CPP/7zip/Compress/DeflateDecoder.cpp +15 -1
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,9 @@ class ArchiveError extends Error {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const defaultMaxOutputBytes = 4n * 1024n * 1024n * 1024n;
|
|
23
|
+
const defaultDeflateBufferSize = 32 * 1024;
|
|
24
|
+
const minDeflateBufferSize = 4 * 1024;
|
|
25
|
+
const maxDeflateBufferSize = 2 * 1024 * 1024;
|
|
23
26
|
function normalizeMaxBytes(value, label) {
|
|
24
27
|
if (value === undefined) {
|
|
25
28
|
return defaultMaxOutputBytes;
|
|
@@ -63,32 +66,44 @@ function safeEntrySegments(entryPath) {
|
|
|
63
66
|
throw new ArchiveError('ERR_ARCHIVE_PATH', 'Archive entry path contains a null byte');
|
|
64
67
|
}
|
|
65
68
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
66
|
-
if (normalized.startsWith('/') || /^[a-zA-Z]
|
|
69
|
+
if (normalized.startsWith('/') || /^[a-zA-Z]:\//.test(normalized)) {
|
|
67
70
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Archive entry path is absolute: ${entryPath}`);
|
|
68
71
|
}
|
|
69
72
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
70
|
-
if (segments.length === 0 || segments.some(segment => segment === '..'
|
|
71
|
-
|| segment.includes(':')
|
|
72
|
-
|| /[. ]$/.test(segment)
|
|
73
|
-
|| /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(segment))) {
|
|
73
|
+
if (segments.length === 0 || segments.some(segment => segment === '..')) {
|
|
74
74
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Unsafe archive entry path: ${entryPath}`);
|
|
75
75
|
}
|
|
76
76
|
return segments;
|
|
77
77
|
}
|
|
78
|
+
function sanitizeWindowsSegment(segment) {
|
|
79
|
+
if (segment === '.' || segment === '..') {
|
|
80
|
+
return segment;
|
|
81
|
+
}
|
|
82
|
+
const sanitized = [...segment].map(character => character.charCodeAt(0) < 0x20 || '<>:|?*"'.includes(character) ? '_' : character).join('')
|
|
83
|
+
.replace(/[. ]+$/g, trailing => '_'.repeat(trailing.length));
|
|
84
|
+
const reservedName = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])/i.exec(sanitized);
|
|
85
|
+
if (reservedName) {
|
|
86
|
+
let index = reservedName[0].length;
|
|
87
|
+
while (sanitized[index] === ' ') {
|
|
88
|
+
index++;
|
|
89
|
+
}
|
|
90
|
+
if (index === sanitized.length || sanitized[index] === '.') {
|
|
91
|
+
return `_${sanitized}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return sanitized;
|
|
95
|
+
}
|
|
78
96
|
function sanitizeOutputPath(entryPath) {
|
|
79
97
|
if (process.platform !== 'win32') {
|
|
80
98
|
return entryPath;
|
|
81
99
|
}
|
|
82
|
-
return entryPath
|
|
83
|
-
.replace(/([. ]+)([\\/]|$)/g, (_match, unsupported, separator) => '_'.repeat(unsupported.length) + separator)
|
|
84
|
-
.replace(/[<>:|?*"]/g, '_');
|
|
100
|
+
return entryPath.replace(/\\/g, '/').split('/').map(sanitizeWindowsSegment).join('/');
|
|
85
101
|
}
|
|
86
102
|
function assertSafeRelativeArchivePath(entryPath) {
|
|
87
103
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
88
104
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
89
|
-
if (
|
|
90
|
-
|| normalized
|
|
91
|
-
|| /^[a-zA-Z]:/.test(normalized)
|
|
105
|
+
if (normalized.startsWith('/')
|
|
106
|
+
|| /^[a-zA-Z]:\//.test(normalized)
|
|
92
107
|
|| segments.length === 0
|
|
93
108
|
|| segments.some(segment => segment === '..')) {
|
|
94
109
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Unsafe archive entry path: ${entryPath}`);
|
|
@@ -159,6 +174,19 @@ async function assertReplaceableOutput(filePath) {
|
|
|
159
174
|
}
|
|
160
175
|
}
|
|
161
176
|
}
|
|
177
|
+
async function assertFolderOutputDoesNotExist(filePath) {
|
|
178
|
+
try {
|
|
179
|
+
await promises.lstat(filePath);
|
|
180
|
+
const error = new Error(`Output file already exists: ${filePath}`);
|
|
181
|
+
Object.assign(error, { code: 'EEXIST' });
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error.code !== 'ENOENT') {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
162
190
|
async function assertOutputDoesNotExist(filePath) {
|
|
163
191
|
try {
|
|
164
192
|
await promises.lstat(filePath);
|
|
@@ -188,7 +216,7 @@ async function normalizeCreateEntry(entry) {
|
|
|
188
216
|
if (details.isSymbolicLink() || !details.isFile()) {
|
|
189
217
|
throw new ArchiveError('ERR_ARCHIVE_CREATE', `Archive creation source is not a regular file: ${sourcePath}`);
|
|
190
218
|
}
|
|
191
|
-
return { sourcePath, path: internalPath };
|
|
219
|
+
return { sourcePath, path: internalPath, mtimeMs: details.mtimeMs };
|
|
192
220
|
}
|
|
193
221
|
async function ensureExtractionRoot(root) {
|
|
194
222
|
try {
|
|
@@ -318,6 +346,7 @@ class Archive {
|
|
|
318
346
|
handle;
|
|
319
347
|
closed = false;
|
|
320
348
|
entriesPromise;
|
|
349
|
+
entryIndexes;
|
|
321
350
|
constructor(handle) {
|
|
322
351
|
this.handle = handle;
|
|
323
352
|
}
|
|
@@ -342,8 +371,14 @@ class Archive {
|
|
|
342
371
|
throw new RangeError('Password exceeds the 1 MiB native password limit');
|
|
343
372
|
}
|
|
344
373
|
}
|
|
374
|
+
const deflateBufferSize = options.deflateBufferSize ?? defaultDeflateBufferSize;
|
|
375
|
+
if (!Number.isSafeInteger(deflateBufferSize)
|
|
376
|
+
|| deflateBufferSize < minDeflateBufferSize
|
|
377
|
+
|| deflateBufferSize > maxDeflateBufferSize) {
|
|
378
|
+
throw new RangeError(`Deflate buffer size must be an integer between ${minDeflateBufferSize} and ${maxDeflateBufferSize} bytes`);
|
|
379
|
+
}
|
|
345
380
|
try {
|
|
346
|
-
const handle = await native.open(node_path.resolve(path), options.password, enginePath);
|
|
381
|
+
const handle = await native.open(node_path.resolve(path), options.password, enginePath, deflateBufferSize);
|
|
347
382
|
return new Archive(handle);
|
|
348
383
|
}
|
|
349
384
|
catch (cause) {
|
|
@@ -455,7 +490,23 @@ class Archive {
|
|
|
455
490
|
if (typeof idOrPath === 'number' && (!Number.isSafeInteger(idOrPath) || idOrPath < 0)) {
|
|
456
491
|
throw new RangeError('Entry ID must be a non-negative safe integer');
|
|
457
492
|
}
|
|
458
|
-
|
|
493
|
+
const entries = await this.entries();
|
|
494
|
+
if (!this.entryIndexes) {
|
|
495
|
+
const pathEntries = Object.create(null);
|
|
496
|
+
const idEntries = Object.create(null);
|
|
497
|
+
for (const entry of entries) {
|
|
498
|
+
pathEntries[entry.path] ??= entry;
|
|
499
|
+
idEntries[entry.id] ??= entry;
|
|
500
|
+
}
|
|
501
|
+
this.entryIndexes = { pathEntries, idEntries };
|
|
502
|
+
}
|
|
503
|
+
if (typeof idOrPath === 'number') {
|
|
504
|
+
return this.entryIndexes.idEntries[idOrPath];
|
|
505
|
+
}
|
|
506
|
+
if (typeof idOrPath === 'string') {
|
|
507
|
+
return this.entryIndexes.pathEntries[idOrPath];
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
459
510
|
}
|
|
460
511
|
async extract(idOrPathOrPaths) {
|
|
461
512
|
this.assertOpen();
|
|
@@ -533,9 +584,6 @@ class Archive {
|
|
|
533
584
|
|| options.concurrency > 128)) {
|
|
534
585
|
throw new RangeError('Folder extraction concurrency must be an integer between 1 and 128');
|
|
535
586
|
}
|
|
536
|
-
if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
|
|
537
|
-
throw new TypeError('Folder extraction onProgress option must be a function');
|
|
538
|
-
}
|
|
539
587
|
if (options.onExtract !== undefined && typeof options.onExtract !== 'function') {
|
|
540
588
|
throw new TypeError('Folder extraction onExtract option must be a function');
|
|
541
589
|
}
|
|
@@ -578,33 +626,13 @@ class Archive {
|
|
|
578
626
|
}
|
|
579
627
|
const root = node_path.resolve(destination);
|
|
580
628
|
const plans = makeOutputPlans(root, selectedEntries);
|
|
581
|
-
const
|
|
582
|
-
if (maxBytes !== null &&
|
|
629
|
+
const totalBytes = selectedEntries.reduce((total, entry) => total + (entry.isDirectory ? 0n : entry.size), 0n);
|
|
630
|
+
if (maxBytes !== null && totalBytes > maxBytes) {
|
|
583
631
|
throw new ArchiveError('ERR_RESOURCE_LIMIT', `Folder output exceeds the ${maxBytes}-byte limit`);
|
|
584
632
|
}
|
|
585
633
|
options.signal?.throwIfAborted();
|
|
586
634
|
await ensureExtractionRoot(root);
|
|
587
|
-
let extractedBytes = 0n;
|
|
588
635
|
let filesExtracted = 0;
|
|
589
|
-
const totalFiles = plans.filter(plan => !plan.isDirectory).length;
|
|
590
|
-
const reportProgress = () => {
|
|
591
|
-
let percent = 0;
|
|
592
|
-
if (expectedBytes > 0n) {
|
|
593
|
-
const scaledPercent = extractedBytes * 10000n / expectedBytes;
|
|
594
|
-
percent = Number(scaledPercent > 9900n ? 9900n : scaledPercent) / 100;
|
|
595
|
-
}
|
|
596
|
-
if (filesExtracted === totalFiles) {
|
|
597
|
-
percent = 100;
|
|
598
|
-
}
|
|
599
|
-
options.onProgress?.({
|
|
600
|
-
filesExtracted,
|
|
601
|
-
totalFiles,
|
|
602
|
-
bytesProcessed: extractedBytes,
|
|
603
|
-
totalBytes: expectedBytes,
|
|
604
|
-
percent,
|
|
605
|
-
});
|
|
606
|
-
};
|
|
607
|
-
reportProgress();
|
|
608
636
|
for (const plan of plans) {
|
|
609
637
|
options.signal?.throwIfAborted();
|
|
610
638
|
if (plan.isDirectory) {
|
|
@@ -616,7 +644,6 @@ class Archive {
|
|
|
616
644
|
const extractionSignal = controller.signal;
|
|
617
645
|
let hasFailure = false;
|
|
618
646
|
let failure;
|
|
619
|
-
let nextFileIndex = 0;
|
|
620
647
|
const stop = (error) => {
|
|
621
648
|
if (!hasFailure) {
|
|
622
649
|
hasFailure = true;
|
|
@@ -630,78 +657,77 @@ class Archive {
|
|
|
630
657
|
stop(options.signal?.reason);
|
|
631
658
|
};
|
|
632
659
|
options.signal?.addEventListener('abort', abortFromCaller, { once: true });
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
660
|
+
const groupCount = Math.min(concurrency, filePlans.length);
|
|
661
|
+
const fileGroups = Array.from({ length: groupCount }, () => []);
|
|
662
|
+
filePlans.forEach((plan, index) => {
|
|
663
|
+
fileGroups[index % groupCount].push(plan);
|
|
664
|
+
});
|
|
665
|
+
const extractGroup = async (group) => {
|
|
666
|
+
const outputs = [];
|
|
667
|
+
let completionFailed = false;
|
|
668
|
+
let completionFailure;
|
|
669
|
+
try {
|
|
670
|
+
for (const plan of group) {
|
|
671
|
+
await ensureOutputParents(root, plan.targetPath);
|
|
672
|
+
extractionSignal.throwIfAborted();
|
|
673
|
+
if (options.overwrite) {
|
|
674
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
645
675
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return;
|
|
676
|
+
else {
|
|
677
|
+
await assertFolderOutputDoesNotExist(plan.targetPath);
|
|
649
678
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const temporaryPath = node_path.join(node_path.dirname(plan.targetPath), `.7zip-native-${node_crypto.randomUUID()}.tmp`);
|
|
656
|
-
try {
|
|
657
|
-
await promises$1.pipeline(this.createReadStream(plan.entry.id, {
|
|
658
|
-
signal: extractionSignal,
|
|
659
|
-
maxBytes: null,
|
|
660
|
-
}), limit, node_fs.createWriteStream(temporaryPath, { flags: 'wx', mode: 0o600 }), { signal: extractionSignal });
|
|
661
|
-
extractionSignal.throwIfAborted();
|
|
662
|
-
await assertReplaceableOutput(plan.targetPath);
|
|
663
|
-
extractionSignal.throwIfAborted();
|
|
664
|
-
await promises.rename(temporaryPath, plan.targetPath);
|
|
665
|
-
}
|
|
666
|
-
catch (error) {
|
|
667
|
-
await promises.rm(temporaryPath, { force: true });
|
|
668
|
-
throw error;
|
|
679
|
+
outputs.push({
|
|
680
|
+
id: plan.entry.id,
|
|
681
|
+
path: plan.targetPath,
|
|
682
|
+
overwrite: options.overwrite === true,
|
|
683
|
+
});
|
|
669
684
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
685
|
+
let onFileCompleted;
|
|
686
|
+
const onExtract = options.onExtract;
|
|
687
|
+
if (onExtract) {
|
|
688
|
+
const plansById = new Map(group.map(plan => [plan.entry.id, plan]));
|
|
689
|
+
onFileCompleted = (id) => {
|
|
690
|
+
if (completionFailed || hasFailure) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const plan = plansById.get(id);
|
|
695
|
+
if (plan === undefined) {
|
|
696
|
+
throw new ArchiveError('ERR_ARCHIVE_EXTRACT', 'Native folder extraction returned an unknown file completion');
|
|
697
|
+
}
|
|
698
|
+
extractionSignal.throwIfAborted();
|
|
699
|
+
filesExtracted++;
|
|
700
|
+
onExtract({
|
|
701
|
+
entry: plan.entry,
|
|
702
|
+
outputPath: plan.targetPath,
|
|
703
|
+
percent: filesExtracted * 100 / filePlans.length,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
completionFailed = true;
|
|
708
|
+
completionFailure = error;
|
|
709
|
+
stop(error);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
693
712
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
713
|
+
await this.extractEntriesToFiles(outputs, extractionSignal, onFileCompleted);
|
|
714
|
+
if (completionFailed) {
|
|
715
|
+
throw completionFailure;
|
|
697
716
|
}
|
|
698
717
|
}
|
|
718
|
+
catch (caughtError) {
|
|
719
|
+
throw completionFailed ? completionFailure : caughtError;
|
|
720
|
+
}
|
|
699
721
|
};
|
|
700
722
|
try {
|
|
701
723
|
options.signal?.throwIfAborted();
|
|
702
|
-
const workers =
|
|
703
|
-
|
|
704
|
-
|
|
724
|
+
const workers = fileGroups.map(group => extractGroup(group));
|
|
725
|
+
try {
|
|
726
|
+
await Promise.all(workers);
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
stop(error);
|
|
730
|
+
await Promise.allSettled(workers);
|
|
705
731
|
throw failure;
|
|
706
732
|
}
|
|
707
733
|
}
|
|
@@ -709,8 +735,67 @@ class Archive {
|
|
|
709
735
|
options.signal?.removeEventListener('abort', abortFromCaller);
|
|
710
736
|
}
|
|
711
737
|
}
|
|
712
|
-
async read(
|
|
738
|
+
async read(idOrPathOrPaths, options) {
|
|
713
739
|
this.assertOpen();
|
|
740
|
+
const maxUInt64 = (1n << 64n) - 1n;
|
|
741
|
+
const normalizeRange = (range) => {
|
|
742
|
+
const offset = this.toUnsignedBigInt(range.offset ?? 0, 'offset');
|
|
743
|
+
const length = this.toUnsignedBigInt(range.length, 'length');
|
|
744
|
+
if (offset > maxUInt64 || length > maxUInt64 - offset) {
|
|
745
|
+
throw new ArchiveError('ERR_INVALID_RANGE', 'Read range exceeds the native 64-bit offset limit');
|
|
746
|
+
}
|
|
747
|
+
return { offset, length };
|
|
748
|
+
};
|
|
749
|
+
if (options === undefined) {
|
|
750
|
+
if (!Array.isArray(idOrPathOrPaths)) {
|
|
751
|
+
throw new TypeError('Read options are required for an entry selector');
|
|
752
|
+
}
|
|
753
|
+
const requests = idOrPathOrPaths;
|
|
754
|
+
const normalized = await Promise.all(requests.map(async (request) => {
|
|
755
|
+
if (!request || typeof request !== 'object' || !('entry' in request)) {
|
|
756
|
+
throw new TypeError('Batch read requests must include an entry selector');
|
|
757
|
+
}
|
|
758
|
+
const range = normalizeRange(request);
|
|
759
|
+
return {
|
|
760
|
+
id: await this.resolveEntryId(request.entry),
|
|
761
|
+
...range,
|
|
762
|
+
};
|
|
763
|
+
}));
|
|
764
|
+
if (!normalized.length) {
|
|
765
|
+
return [];
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
return await native.readMany(this.handle, normalized);
|
|
769
|
+
}
|
|
770
|
+
catch (cause) {
|
|
771
|
+
throw extractionError(cause);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const isBatch = Array.isArray(idOrPathOrPaths);
|
|
775
|
+
let selectors;
|
|
776
|
+
if (isBatch) {
|
|
777
|
+
selectors = idOrPathOrPaths;
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
selectors = [idOrPathOrPaths];
|
|
781
|
+
}
|
|
782
|
+
const { offset, length } = normalizeRange(options);
|
|
783
|
+
if (isBatch) {
|
|
784
|
+
const ids = await Promise.all(selectors.map(selector => this.resolveEntryId(selector)));
|
|
785
|
+
const uniqueIds = [...new Set(ids)];
|
|
786
|
+
if (!uniqueIds.length) {
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
const buffers = await native.readMany(this.handle, uniqueIds.map(id => ({ id, offset, length })));
|
|
791
|
+
const buffersById = new Map(uniqueIds.map((id, index) => [id, buffers[index]]));
|
|
792
|
+
return ids.map(id => buffersById.get(id));
|
|
793
|
+
}
|
|
794
|
+
catch (cause) {
|
|
795
|
+
throw extractionError(cause);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const idOrPath = selectors[0];
|
|
714
799
|
let id;
|
|
715
800
|
if (typeof idOrPath === 'number') {
|
|
716
801
|
this.validateIds([idOrPath]);
|
|
@@ -719,12 +804,6 @@ class Archive {
|
|
|
719
804
|
else {
|
|
720
805
|
id = await this.resolveEntryId(idOrPath);
|
|
721
806
|
}
|
|
722
|
-
const offset = this.toUnsignedBigInt(options.offset ?? 0, 'offset');
|
|
723
|
-
const length = this.toUnsignedBigInt(options.length, 'length');
|
|
724
|
-
const maxUInt64 = (1n << 64n) - 1n;
|
|
725
|
-
if (offset > maxUInt64 || length > maxUInt64 - offset) {
|
|
726
|
-
throw new ArchiveError('ERR_INVALID_RANGE', 'Read range exceeds the native 64-bit offset limit');
|
|
727
|
-
}
|
|
728
807
|
try {
|
|
729
808
|
return await native.read(this.handle, id, offset, length);
|
|
730
809
|
}
|
|
@@ -732,6 +811,32 @@ class Archive {
|
|
|
732
811
|
throw extractionError(cause);
|
|
733
812
|
}
|
|
734
813
|
}
|
|
814
|
+
async extractEntriesToFiles(outputs, signal, onFileCompleted) {
|
|
815
|
+
let task;
|
|
816
|
+
const cancelTask = () => {
|
|
817
|
+
if (task) {
|
|
818
|
+
native.cancelExtractToFiles(task.taskId);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
try {
|
|
822
|
+
signal.throwIfAborted();
|
|
823
|
+
task = native.startExtractToFiles(this.handle, outputs, onFileCompleted);
|
|
824
|
+
signal.addEventListener('abort', cancelTask, { once: true });
|
|
825
|
+
if (signal.aborted) {
|
|
826
|
+
cancelTask();
|
|
827
|
+
}
|
|
828
|
+
return await task.promise;
|
|
829
|
+
}
|
|
830
|
+
catch (cause) {
|
|
831
|
+
if (signal.aborted) {
|
|
832
|
+
throw signal.reason ?? cause;
|
|
833
|
+
}
|
|
834
|
+
throw extractionError(cause);
|
|
835
|
+
}
|
|
836
|
+
finally {
|
|
837
|
+
signal.removeEventListener('abort', cancelTask);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
735
840
|
async close() {
|
|
736
841
|
if (this.closed) {
|
|
737
842
|
return;
|
package/dist/index.d.cts
CHANGED
|
@@ -16,8 +16,12 @@ interface ReadOptions {
|
|
|
16
16
|
readonly offset?: number | bigint;
|
|
17
17
|
readonly length: number | bigint;
|
|
18
18
|
}
|
|
19
|
+
interface ReadRequest extends ReadOptions {
|
|
20
|
+
readonly entry: EntrySelector;
|
|
21
|
+
}
|
|
19
22
|
interface OpenOptions {
|
|
20
23
|
readonly password?: string;
|
|
24
|
+
readonly deflateBufferSize?: number;
|
|
21
25
|
}
|
|
22
26
|
type ArchiveFormat = '7z' | 'zip' | 'tar' | 'wim';
|
|
23
27
|
interface ArchiveCreateEntry {
|
|
@@ -29,16 +33,10 @@ interface CreateArchiveOptions {
|
|
|
29
33
|
readonly compressionLevel?: number;
|
|
30
34
|
readonly overwrite?: boolean;
|
|
31
35
|
}
|
|
32
|
-
interface ExtractToFolderProgress {
|
|
33
|
-
readonly filesExtracted: number;
|
|
34
|
-
readonly totalFiles: number;
|
|
35
|
-
readonly bytesProcessed: bigint;
|
|
36
|
-
readonly totalBytes: bigint;
|
|
37
|
-
readonly percent: number;
|
|
38
|
-
}
|
|
39
36
|
interface ExtractedArchiveFile {
|
|
40
37
|
readonly entry: ArchiveEntry;
|
|
41
38
|
readonly outputPath: string;
|
|
39
|
+
readonly percent: number;
|
|
42
40
|
}
|
|
43
41
|
interface ReadStreamOptions {
|
|
44
42
|
readonly highWaterMark?: number;
|
|
@@ -50,7 +48,6 @@ interface ExtractToFolderOptions {
|
|
|
50
48
|
readonly paths?: readonly string[];
|
|
51
49
|
readonly concurrency?: number;
|
|
52
50
|
readonly overwrite?: boolean;
|
|
53
|
-
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
54
51
|
readonly onExtract?: (result: ExtractedArchiveFile) => void;
|
|
55
52
|
readonly maxBytes?: number | bigint | null;
|
|
56
53
|
readonly signal?: AbortSignal;
|
|
@@ -65,6 +62,7 @@ declare class Archive {
|
|
|
65
62
|
private readonly handle;
|
|
66
63
|
private closed;
|
|
67
64
|
private entriesPromise?;
|
|
65
|
+
private entryIndexes?;
|
|
68
66
|
private constructor();
|
|
69
67
|
static concurrency(value?: number): number;
|
|
70
68
|
static open(path: string, options?: OpenOptions): Promise<Archive>;
|
|
@@ -77,6 +75,9 @@ declare class Archive {
|
|
|
77
75
|
extractToStream(idOrPath: EntrySelector, destination: Writable, options?: ReadStreamOptions): Promise<void>;
|
|
78
76
|
extractToFolder(destination: string, options?: ExtractToFolderOptions): Promise<void>;
|
|
79
77
|
read(idOrPath: EntrySelector, options: ReadOptions): Promise<Buffer>;
|
|
78
|
+
read(idsOrPaths: readonly EntrySelector[], options: ReadOptions): Promise<Buffer[]>;
|
|
79
|
+
read(requests: readonly ReadRequest[]): Promise<Buffer[]>;
|
|
80
|
+
private extractEntriesToFiles;
|
|
80
81
|
close(): Promise<void>;
|
|
81
82
|
private assertOpen;
|
|
82
83
|
private validateIds;
|
|
@@ -86,4 +87,4 @@ declare class Archive {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export { Archive, ArchiveError };
|
|
89
|
-
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions,
|
|
90
|
+
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions, ExtractedArchiveFile, OpenOptions, ReadOptions, ReadRequest, ReadStreamOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -16,8 +16,12 @@ interface ReadOptions {
|
|
|
16
16
|
readonly offset?: number | bigint;
|
|
17
17
|
readonly length: number | bigint;
|
|
18
18
|
}
|
|
19
|
+
interface ReadRequest extends ReadOptions {
|
|
20
|
+
readonly entry: EntrySelector;
|
|
21
|
+
}
|
|
19
22
|
interface OpenOptions {
|
|
20
23
|
readonly password?: string;
|
|
24
|
+
readonly deflateBufferSize?: number;
|
|
21
25
|
}
|
|
22
26
|
type ArchiveFormat = '7z' | 'zip' | 'tar' | 'wim';
|
|
23
27
|
interface ArchiveCreateEntry {
|
|
@@ -29,16 +33,10 @@ interface CreateArchiveOptions {
|
|
|
29
33
|
readonly compressionLevel?: number;
|
|
30
34
|
readonly overwrite?: boolean;
|
|
31
35
|
}
|
|
32
|
-
interface ExtractToFolderProgress {
|
|
33
|
-
readonly filesExtracted: number;
|
|
34
|
-
readonly totalFiles: number;
|
|
35
|
-
readonly bytesProcessed: bigint;
|
|
36
|
-
readonly totalBytes: bigint;
|
|
37
|
-
readonly percent: number;
|
|
38
|
-
}
|
|
39
36
|
interface ExtractedArchiveFile {
|
|
40
37
|
readonly entry: ArchiveEntry;
|
|
41
38
|
readonly outputPath: string;
|
|
39
|
+
readonly percent: number;
|
|
42
40
|
}
|
|
43
41
|
interface ReadStreamOptions {
|
|
44
42
|
readonly highWaterMark?: number;
|
|
@@ -50,7 +48,6 @@ interface ExtractToFolderOptions {
|
|
|
50
48
|
readonly paths?: readonly string[];
|
|
51
49
|
readonly concurrency?: number;
|
|
52
50
|
readonly overwrite?: boolean;
|
|
53
|
-
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
54
51
|
readonly onExtract?: (result: ExtractedArchiveFile) => void;
|
|
55
52
|
readonly maxBytes?: number | bigint | null;
|
|
56
53
|
readonly signal?: AbortSignal;
|
|
@@ -65,6 +62,7 @@ declare class Archive {
|
|
|
65
62
|
private readonly handle;
|
|
66
63
|
private closed;
|
|
67
64
|
private entriesPromise?;
|
|
65
|
+
private entryIndexes?;
|
|
68
66
|
private constructor();
|
|
69
67
|
static concurrency(value?: number): number;
|
|
70
68
|
static open(path: string, options?: OpenOptions): Promise<Archive>;
|
|
@@ -77,6 +75,9 @@ declare class Archive {
|
|
|
77
75
|
extractToStream(idOrPath: EntrySelector, destination: Writable, options?: ReadStreamOptions): Promise<void>;
|
|
78
76
|
extractToFolder(destination: string, options?: ExtractToFolderOptions): Promise<void>;
|
|
79
77
|
read(idOrPath: EntrySelector, options: ReadOptions): Promise<Buffer>;
|
|
78
|
+
read(idsOrPaths: readonly EntrySelector[], options: ReadOptions): Promise<Buffer[]>;
|
|
79
|
+
read(requests: readonly ReadRequest[]): Promise<Buffer[]>;
|
|
80
|
+
private extractEntriesToFiles;
|
|
80
81
|
close(): Promise<void>;
|
|
81
82
|
private assertOpen;
|
|
82
83
|
private validateIds;
|
|
@@ -86,4 +87,4 @@ declare class Archive {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export { Archive, ArchiveError };
|
|
89
|
-
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions,
|
|
90
|
+
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions, ExtractedArchiveFile, OpenOptions, ReadOptions, ReadRequest, ReadStreamOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,12 @@ interface ReadOptions {
|
|
|
16
16
|
readonly offset?: number | bigint;
|
|
17
17
|
readonly length: number | bigint;
|
|
18
18
|
}
|
|
19
|
+
interface ReadRequest extends ReadOptions {
|
|
20
|
+
readonly entry: EntrySelector;
|
|
21
|
+
}
|
|
19
22
|
interface OpenOptions {
|
|
20
23
|
readonly password?: string;
|
|
24
|
+
readonly deflateBufferSize?: number;
|
|
21
25
|
}
|
|
22
26
|
type ArchiveFormat = '7z' | 'zip' | 'tar' | 'wim';
|
|
23
27
|
interface ArchiveCreateEntry {
|
|
@@ -29,16 +33,10 @@ interface CreateArchiveOptions {
|
|
|
29
33
|
readonly compressionLevel?: number;
|
|
30
34
|
readonly overwrite?: boolean;
|
|
31
35
|
}
|
|
32
|
-
interface ExtractToFolderProgress {
|
|
33
|
-
readonly filesExtracted: number;
|
|
34
|
-
readonly totalFiles: number;
|
|
35
|
-
readonly bytesProcessed: bigint;
|
|
36
|
-
readonly totalBytes: bigint;
|
|
37
|
-
readonly percent: number;
|
|
38
|
-
}
|
|
39
36
|
interface ExtractedArchiveFile {
|
|
40
37
|
readonly entry: ArchiveEntry;
|
|
41
38
|
readonly outputPath: string;
|
|
39
|
+
readonly percent: number;
|
|
42
40
|
}
|
|
43
41
|
interface ReadStreamOptions {
|
|
44
42
|
readonly highWaterMark?: number;
|
|
@@ -50,7 +48,6 @@ interface ExtractToFolderOptions {
|
|
|
50
48
|
readonly paths?: readonly string[];
|
|
51
49
|
readonly concurrency?: number;
|
|
52
50
|
readonly overwrite?: boolean;
|
|
53
|
-
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
54
51
|
readonly onExtract?: (result: ExtractedArchiveFile) => void;
|
|
55
52
|
readonly maxBytes?: number | bigint | null;
|
|
56
53
|
readonly signal?: AbortSignal;
|
|
@@ -65,6 +62,7 @@ declare class Archive {
|
|
|
65
62
|
private readonly handle;
|
|
66
63
|
private closed;
|
|
67
64
|
private entriesPromise?;
|
|
65
|
+
private entryIndexes?;
|
|
68
66
|
private constructor();
|
|
69
67
|
static concurrency(value?: number): number;
|
|
70
68
|
static open(path: string, options?: OpenOptions): Promise<Archive>;
|
|
@@ -77,6 +75,9 @@ declare class Archive {
|
|
|
77
75
|
extractToStream(idOrPath: EntrySelector, destination: Writable, options?: ReadStreamOptions): Promise<void>;
|
|
78
76
|
extractToFolder(destination: string, options?: ExtractToFolderOptions): Promise<void>;
|
|
79
77
|
read(idOrPath: EntrySelector, options: ReadOptions): Promise<Buffer>;
|
|
78
|
+
read(idsOrPaths: readonly EntrySelector[], options: ReadOptions): Promise<Buffer[]>;
|
|
79
|
+
read(requests: readonly ReadRequest[]): Promise<Buffer[]>;
|
|
80
|
+
private extractEntriesToFiles;
|
|
80
81
|
close(): Promise<void>;
|
|
81
82
|
private assertOpen;
|
|
82
83
|
private validateIds;
|
|
@@ -86,4 +87,4 @@ declare class Archive {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export { Archive, ArchiveError };
|
|
89
|
-
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions,
|
|
90
|
+
export type { ArchiveCreateEntry, ArchiveEntry, ArchiveErrorCode, ArchiveFormat, CreateArchiveOptions, EntryId, EntrySelector, ExtractToFolderOptions, ExtractedArchiveFile, OpenOptions, ReadOptions, ReadRequest, ReadStreamOptions };
|