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.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import { rename, link, rm, lstat, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { availableParallelism } from 'node:os';
|
|
6
6
|
import { dirname, resolve, join, extname, relative, sep } from 'node:path';
|
|
7
|
-
import { Readable
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
8
|
import { pipeline } from 'node:stream/promises';
|
|
9
9
|
|
|
10
10
|
class ArchiveError extends Error {
|
|
@@ -17,6 +17,9 @@ class ArchiveError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const defaultMaxOutputBytes = 4n * 1024n * 1024n * 1024n;
|
|
20
|
+
const defaultDeflateBufferSize = 32 * 1024;
|
|
21
|
+
const minDeflateBufferSize = 4 * 1024;
|
|
22
|
+
const maxDeflateBufferSize = 2 * 1024 * 1024;
|
|
20
23
|
function normalizeMaxBytes(value, label) {
|
|
21
24
|
if (value === undefined) {
|
|
22
25
|
return defaultMaxOutputBytes;
|
|
@@ -60,32 +63,44 @@ function safeEntrySegments(entryPath) {
|
|
|
60
63
|
throw new ArchiveError('ERR_ARCHIVE_PATH', 'Archive entry path contains a null byte');
|
|
61
64
|
}
|
|
62
65
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
63
|
-
if (normalized.startsWith('/') || /^[a-zA-Z]
|
|
66
|
+
if (normalized.startsWith('/') || /^[a-zA-Z]:\//.test(normalized)) {
|
|
64
67
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Archive entry path is absolute: ${entryPath}`);
|
|
65
68
|
}
|
|
66
69
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
67
|
-
if (segments.length === 0 || segments.some(segment => segment === '..'
|
|
68
|
-
|| segment.includes(':')
|
|
69
|
-
|| /[. ]$/.test(segment)
|
|
70
|
-
|| /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(segment))) {
|
|
70
|
+
if (segments.length === 0 || segments.some(segment => segment === '..')) {
|
|
71
71
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Unsafe archive entry path: ${entryPath}`);
|
|
72
72
|
}
|
|
73
73
|
return segments;
|
|
74
74
|
}
|
|
75
|
+
function sanitizeWindowsSegment(segment) {
|
|
76
|
+
if (segment === '.' || segment === '..') {
|
|
77
|
+
return segment;
|
|
78
|
+
}
|
|
79
|
+
const sanitized = [...segment].map(character => character.charCodeAt(0) < 0x20 || '<>:|?*"'.includes(character) ? '_' : character).join('')
|
|
80
|
+
.replace(/[. ]+$/g, trailing => '_'.repeat(trailing.length));
|
|
81
|
+
const reservedName = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])/i.exec(sanitized);
|
|
82
|
+
if (reservedName) {
|
|
83
|
+
let index = reservedName[0].length;
|
|
84
|
+
while (sanitized[index] === ' ') {
|
|
85
|
+
index++;
|
|
86
|
+
}
|
|
87
|
+
if (index === sanitized.length || sanitized[index] === '.') {
|
|
88
|
+
return `_${sanitized}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return sanitized;
|
|
92
|
+
}
|
|
75
93
|
function sanitizeOutputPath(entryPath) {
|
|
76
94
|
if (process.platform !== 'win32') {
|
|
77
95
|
return entryPath;
|
|
78
96
|
}
|
|
79
|
-
return entryPath
|
|
80
|
-
.replace(/([. ]+)([\\/]|$)/g, (_match, unsupported, separator) => '_'.repeat(unsupported.length) + separator)
|
|
81
|
-
.replace(/[<>:|?*"]/g, '_');
|
|
97
|
+
return entryPath.replace(/\\/g, '/').split('/').map(sanitizeWindowsSegment).join('/');
|
|
82
98
|
}
|
|
83
99
|
function assertSafeRelativeArchivePath(entryPath) {
|
|
84
100
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
85
101
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
86
|
-
if (
|
|
87
|
-
|| normalized
|
|
88
|
-
|| /^[a-zA-Z]:/.test(normalized)
|
|
102
|
+
if (normalized.startsWith('/')
|
|
103
|
+
|| /^[a-zA-Z]:\//.test(normalized)
|
|
89
104
|
|| segments.length === 0
|
|
90
105
|
|| segments.some(segment => segment === '..')) {
|
|
91
106
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Unsafe archive entry path: ${entryPath}`);
|
|
@@ -156,6 +171,19 @@ async function assertReplaceableOutput(filePath) {
|
|
|
156
171
|
}
|
|
157
172
|
}
|
|
158
173
|
}
|
|
174
|
+
async function assertFolderOutputDoesNotExist(filePath) {
|
|
175
|
+
try {
|
|
176
|
+
await lstat(filePath);
|
|
177
|
+
const error = new Error(`Output file already exists: ${filePath}`);
|
|
178
|
+
Object.assign(error, { code: 'EEXIST' });
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (error.code !== 'ENOENT') {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
159
187
|
async function assertOutputDoesNotExist(filePath) {
|
|
160
188
|
try {
|
|
161
189
|
await lstat(filePath);
|
|
@@ -185,7 +213,7 @@ async function normalizeCreateEntry(entry) {
|
|
|
185
213
|
if (details.isSymbolicLink() || !details.isFile()) {
|
|
186
214
|
throw new ArchiveError('ERR_ARCHIVE_CREATE', `Archive creation source is not a regular file: ${sourcePath}`);
|
|
187
215
|
}
|
|
188
|
-
return { sourcePath, path: internalPath };
|
|
216
|
+
return { sourcePath, path: internalPath, mtimeMs: details.mtimeMs };
|
|
189
217
|
}
|
|
190
218
|
async function ensureExtractionRoot(root) {
|
|
191
219
|
try {
|
|
@@ -315,6 +343,7 @@ class Archive {
|
|
|
315
343
|
handle;
|
|
316
344
|
closed = false;
|
|
317
345
|
entriesPromise;
|
|
346
|
+
entryIndexes;
|
|
318
347
|
constructor(handle) {
|
|
319
348
|
this.handle = handle;
|
|
320
349
|
}
|
|
@@ -339,8 +368,14 @@ class Archive {
|
|
|
339
368
|
throw new RangeError('Password exceeds the 1 MiB native password limit');
|
|
340
369
|
}
|
|
341
370
|
}
|
|
371
|
+
const deflateBufferSize = options.deflateBufferSize ?? defaultDeflateBufferSize;
|
|
372
|
+
if (!Number.isSafeInteger(deflateBufferSize)
|
|
373
|
+
|| deflateBufferSize < minDeflateBufferSize
|
|
374
|
+
|| deflateBufferSize > maxDeflateBufferSize) {
|
|
375
|
+
throw new RangeError(`Deflate buffer size must be an integer between ${minDeflateBufferSize} and ${maxDeflateBufferSize} bytes`);
|
|
376
|
+
}
|
|
342
377
|
try {
|
|
343
|
-
const handle = await native.open(resolve(path), options.password, enginePath);
|
|
378
|
+
const handle = await native.open(resolve(path), options.password, enginePath, deflateBufferSize);
|
|
344
379
|
return new Archive(handle);
|
|
345
380
|
}
|
|
346
381
|
catch (cause) {
|
|
@@ -452,7 +487,23 @@ class Archive {
|
|
|
452
487
|
if (typeof idOrPath === 'number' && (!Number.isSafeInteger(idOrPath) || idOrPath < 0)) {
|
|
453
488
|
throw new RangeError('Entry ID must be a non-negative safe integer');
|
|
454
489
|
}
|
|
455
|
-
|
|
490
|
+
const entries = await this.entries();
|
|
491
|
+
if (!this.entryIndexes) {
|
|
492
|
+
const pathEntries = Object.create(null);
|
|
493
|
+
const idEntries = Object.create(null);
|
|
494
|
+
for (const entry of entries) {
|
|
495
|
+
pathEntries[entry.path] ??= entry;
|
|
496
|
+
idEntries[entry.id] ??= entry;
|
|
497
|
+
}
|
|
498
|
+
this.entryIndexes = { pathEntries, idEntries };
|
|
499
|
+
}
|
|
500
|
+
if (typeof idOrPath === 'number') {
|
|
501
|
+
return this.entryIndexes.idEntries[idOrPath];
|
|
502
|
+
}
|
|
503
|
+
if (typeof idOrPath === 'string') {
|
|
504
|
+
return this.entryIndexes.pathEntries[idOrPath];
|
|
505
|
+
}
|
|
506
|
+
return undefined;
|
|
456
507
|
}
|
|
457
508
|
async extract(idOrPathOrPaths) {
|
|
458
509
|
this.assertOpen();
|
|
@@ -530,9 +581,6 @@ class Archive {
|
|
|
530
581
|
|| options.concurrency > 128)) {
|
|
531
582
|
throw new RangeError('Folder extraction concurrency must be an integer between 1 and 128');
|
|
532
583
|
}
|
|
533
|
-
if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
|
|
534
|
-
throw new TypeError('Folder extraction onProgress option must be a function');
|
|
535
|
-
}
|
|
536
584
|
if (options.onExtract !== undefined && typeof options.onExtract !== 'function') {
|
|
537
585
|
throw new TypeError('Folder extraction onExtract option must be a function');
|
|
538
586
|
}
|
|
@@ -575,33 +623,13 @@ class Archive {
|
|
|
575
623
|
}
|
|
576
624
|
const root = resolve(destination);
|
|
577
625
|
const plans = makeOutputPlans(root, selectedEntries);
|
|
578
|
-
const
|
|
579
|
-
if (maxBytes !== null &&
|
|
626
|
+
const totalBytes = selectedEntries.reduce((total, entry) => total + (entry.isDirectory ? 0n : entry.size), 0n);
|
|
627
|
+
if (maxBytes !== null && totalBytes > maxBytes) {
|
|
580
628
|
throw new ArchiveError('ERR_RESOURCE_LIMIT', `Folder output exceeds the ${maxBytes}-byte limit`);
|
|
581
629
|
}
|
|
582
630
|
options.signal?.throwIfAborted();
|
|
583
631
|
await ensureExtractionRoot(root);
|
|
584
|
-
let extractedBytes = 0n;
|
|
585
632
|
let filesExtracted = 0;
|
|
586
|
-
const totalFiles = plans.filter(plan => !plan.isDirectory).length;
|
|
587
|
-
const reportProgress = () => {
|
|
588
|
-
let percent = 0;
|
|
589
|
-
if (expectedBytes > 0n) {
|
|
590
|
-
const scaledPercent = extractedBytes * 10000n / expectedBytes;
|
|
591
|
-
percent = Number(scaledPercent > 9900n ? 9900n : scaledPercent) / 100;
|
|
592
|
-
}
|
|
593
|
-
if (filesExtracted === totalFiles) {
|
|
594
|
-
percent = 100;
|
|
595
|
-
}
|
|
596
|
-
options.onProgress?.({
|
|
597
|
-
filesExtracted,
|
|
598
|
-
totalFiles,
|
|
599
|
-
bytesProcessed: extractedBytes,
|
|
600
|
-
totalBytes: expectedBytes,
|
|
601
|
-
percent,
|
|
602
|
-
});
|
|
603
|
-
};
|
|
604
|
-
reportProgress();
|
|
605
633
|
for (const plan of plans) {
|
|
606
634
|
options.signal?.throwIfAborted();
|
|
607
635
|
if (plan.isDirectory) {
|
|
@@ -613,7 +641,6 @@ class Archive {
|
|
|
613
641
|
const extractionSignal = controller.signal;
|
|
614
642
|
let hasFailure = false;
|
|
615
643
|
let failure;
|
|
616
|
-
let nextFileIndex = 0;
|
|
617
644
|
const stop = (error) => {
|
|
618
645
|
if (!hasFailure) {
|
|
619
646
|
hasFailure = true;
|
|
@@ -627,78 +654,77 @@ class Archive {
|
|
|
627
654
|
stop(options.signal?.reason);
|
|
628
655
|
};
|
|
629
656
|
options.signal?.addEventListener('abort', abortFromCaller, { once: true });
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
657
|
+
const groupCount = Math.min(concurrency, filePlans.length);
|
|
658
|
+
const fileGroups = Array.from({ length: groupCount }, () => []);
|
|
659
|
+
filePlans.forEach((plan, index) => {
|
|
660
|
+
fileGroups[index % groupCount].push(plan);
|
|
661
|
+
});
|
|
662
|
+
const extractGroup = async (group) => {
|
|
663
|
+
const outputs = [];
|
|
664
|
+
let completionFailed = false;
|
|
665
|
+
let completionFailure;
|
|
666
|
+
try {
|
|
667
|
+
for (const plan of group) {
|
|
668
|
+
await ensureOutputParents(root, plan.targetPath);
|
|
669
|
+
extractionSignal.throwIfAborted();
|
|
670
|
+
if (options.overwrite) {
|
|
671
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
642
672
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return;
|
|
673
|
+
else {
|
|
674
|
+
await assertFolderOutputDoesNotExist(plan.targetPath);
|
|
646
675
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const temporaryPath = join(dirname(plan.targetPath), `.7zip-native-${randomUUID()}.tmp`);
|
|
653
|
-
try {
|
|
654
|
-
await pipeline(this.createReadStream(plan.entry.id, {
|
|
655
|
-
signal: extractionSignal,
|
|
656
|
-
maxBytes: null,
|
|
657
|
-
}), limit, createWriteStream(temporaryPath, { flags: 'wx', mode: 0o600 }), { signal: extractionSignal });
|
|
658
|
-
extractionSignal.throwIfAborted();
|
|
659
|
-
await assertReplaceableOutput(plan.targetPath);
|
|
660
|
-
extractionSignal.throwIfAborted();
|
|
661
|
-
await rename(temporaryPath, plan.targetPath);
|
|
662
|
-
}
|
|
663
|
-
catch (error) {
|
|
664
|
-
await rm(temporaryPath, { force: true });
|
|
665
|
-
throw error;
|
|
676
|
+
outputs.push({
|
|
677
|
+
id: plan.entry.id,
|
|
678
|
+
path: plan.targetPath,
|
|
679
|
+
overwrite: options.overwrite === true,
|
|
680
|
+
});
|
|
666
681
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
682
|
+
let onFileCompleted;
|
|
683
|
+
const onExtract = options.onExtract;
|
|
684
|
+
if (onExtract) {
|
|
685
|
+
const plansById = new Map(group.map(plan => [plan.entry.id, plan]));
|
|
686
|
+
onFileCompleted = (id) => {
|
|
687
|
+
if (completionFailed || hasFailure) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
const plan = plansById.get(id);
|
|
692
|
+
if (plan === undefined) {
|
|
693
|
+
throw new ArchiveError('ERR_ARCHIVE_EXTRACT', 'Native folder extraction returned an unknown file completion');
|
|
694
|
+
}
|
|
695
|
+
extractionSignal.throwIfAborted();
|
|
696
|
+
filesExtracted++;
|
|
697
|
+
onExtract({
|
|
698
|
+
entry: plan.entry,
|
|
699
|
+
outputPath: plan.targetPath,
|
|
700
|
+
percent: filesExtracted * 100 / filePlans.length,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
completionFailed = true;
|
|
705
|
+
completionFailure = error;
|
|
706
|
+
stop(error);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
690
709
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
710
|
+
await this.extractEntriesToFiles(outputs, extractionSignal, onFileCompleted);
|
|
711
|
+
if (completionFailed) {
|
|
712
|
+
throw completionFailure;
|
|
694
713
|
}
|
|
695
714
|
}
|
|
715
|
+
catch (caughtError) {
|
|
716
|
+
throw completionFailed ? completionFailure : caughtError;
|
|
717
|
+
}
|
|
696
718
|
};
|
|
697
719
|
try {
|
|
698
720
|
options.signal?.throwIfAborted();
|
|
699
|
-
const workers =
|
|
700
|
-
|
|
701
|
-
|
|
721
|
+
const workers = fileGroups.map(group => extractGroup(group));
|
|
722
|
+
try {
|
|
723
|
+
await Promise.all(workers);
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
stop(error);
|
|
727
|
+
await Promise.allSettled(workers);
|
|
702
728
|
throw failure;
|
|
703
729
|
}
|
|
704
730
|
}
|
|
@@ -706,8 +732,67 @@ class Archive {
|
|
|
706
732
|
options.signal?.removeEventListener('abort', abortFromCaller);
|
|
707
733
|
}
|
|
708
734
|
}
|
|
709
|
-
async read(
|
|
735
|
+
async read(idOrPathOrPaths, options) {
|
|
710
736
|
this.assertOpen();
|
|
737
|
+
const maxUInt64 = (1n << 64n) - 1n;
|
|
738
|
+
const normalizeRange = (range) => {
|
|
739
|
+
const offset = this.toUnsignedBigInt(range.offset ?? 0, 'offset');
|
|
740
|
+
const length = this.toUnsignedBigInt(range.length, 'length');
|
|
741
|
+
if (offset > maxUInt64 || length > maxUInt64 - offset) {
|
|
742
|
+
throw new ArchiveError('ERR_INVALID_RANGE', 'Read range exceeds the native 64-bit offset limit');
|
|
743
|
+
}
|
|
744
|
+
return { offset, length };
|
|
745
|
+
};
|
|
746
|
+
if (options === undefined) {
|
|
747
|
+
if (!Array.isArray(idOrPathOrPaths)) {
|
|
748
|
+
throw new TypeError('Read options are required for an entry selector');
|
|
749
|
+
}
|
|
750
|
+
const requests = idOrPathOrPaths;
|
|
751
|
+
const normalized = await Promise.all(requests.map(async (request) => {
|
|
752
|
+
if (!request || typeof request !== 'object' || !('entry' in request)) {
|
|
753
|
+
throw new TypeError('Batch read requests must include an entry selector');
|
|
754
|
+
}
|
|
755
|
+
const range = normalizeRange(request);
|
|
756
|
+
return {
|
|
757
|
+
id: await this.resolveEntryId(request.entry),
|
|
758
|
+
...range,
|
|
759
|
+
};
|
|
760
|
+
}));
|
|
761
|
+
if (!normalized.length) {
|
|
762
|
+
return [];
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
return await native.readMany(this.handle, normalized);
|
|
766
|
+
}
|
|
767
|
+
catch (cause) {
|
|
768
|
+
throw extractionError(cause);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const isBatch = Array.isArray(idOrPathOrPaths);
|
|
772
|
+
let selectors;
|
|
773
|
+
if (isBatch) {
|
|
774
|
+
selectors = idOrPathOrPaths;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
selectors = [idOrPathOrPaths];
|
|
778
|
+
}
|
|
779
|
+
const { offset, length } = normalizeRange(options);
|
|
780
|
+
if (isBatch) {
|
|
781
|
+
const ids = await Promise.all(selectors.map(selector => this.resolveEntryId(selector)));
|
|
782
|
+
const uniqueIds = [...new Set(ids)];
|
|
783
|
+
if (!uniqueIds.length) {
|
|
784
|
+
return [];
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const buffers = await native.readMany(this.handle, uniqueIds.map(id => ({ id, offset, length })));
|
|
788
|
+
const buffersById = new Map(uniqueIds.map((id, index) => [id, buffers[index]]));
|
|
789
|
+
return ids.map(id => buffersById.get(id));
|
|
790
|
+
}
|
|
791
|
+
catch (cause) {
|
|
792
|
+
throw extractionError(cause);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const idOrPath = selectors[0];
|
|
711
796
|
let id;
|
|
712
797
|
if (typeof idOrPath === 'number') {
|
|
713
798
|
this.validateIds([idOrPath]);
|
|
@@ -716,12 +801,6 @@ class Archive {
|
|
|
716
801
|
else {
|
|
717
802
|
id = await this.resolveEntryId(idOrPath);
|
|
718
803
|
}
|
|
719
|
-
const offset = this.toUnsignedBigInt(options.offset ?? 0, 'offset');
|
|
720
|
-
const length = this.toUnsignedBigInt(options.length, 'length');
|
|
721
|
-
const maxUInt64 = (1n << 64n) - 1n;
|
|
722
|
-
if (offset > maxUInt64 || length > maxUInt64 - offset) {
|
|
723
|
-
throw new ArchiveError('ERR_INVALID_RANGE', 'Read range exceeds the native 64-bit offset limit');
|
|
724
|
-
}
|
|
725
804
|
try {
|
|
726
805
|
return await native.read(this.handle, id, offset, length);
|
|
727
806
|
}
|
|
@@ -729,6 +808,32 @@ class Archive {
|
|
|
729
808
|
throw extractionError(cause);
|
|
730
809
|
}
|
|
731
810
|
}
|
|
811
|
+
async extractEntriesToFiles(outputs, signal, onFileCompleted) {
|
|
812
|
+
let task;
|
|
813
|
+
const cancelTask = () => {
|
|
814
|
+
if (task) {
|
|
815
|
+
native.cancelExtractToFiles(task.taskId);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
try {
|
|
819
|
+
signal.throwIfAborted();
|
|
820
|
+
task = native.startExtractToFiles(this.handle, outputs, onFileCompleted);
|
|
821
|
+
signal.addEventListener('abort', cancelTask, { once: true });
|
|
822
|
+
if (signal.aborted) {
|
|
823
|
+
cancelTask();
|
|
824
|
+
}
|
|
825
|
+
return await task.promise;
|
|
826
|
+
}
|
|
827
|
+
catch (cause) {
|
|
828
|
+
if (signal.aborted) {
|
|
829
|
+
throw signal.reason ?? cause;
|
|
830
|
+
}
|
|
831
|
+
throw extractionError(cause);
|
|
832
|
+
}
|
|
833
|
+
finally {
|
|
834
|
+
signal.removeEventListener('abort', cancelTask);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
732
837
|
async close() {
|
|
733
838
|
if (this.closed) {
|
|
734
839
|
return;
|