@cj-tech-master/excelts 6.0.0-beta.4 → 6.0.0-beta.5

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.
Files changed (65) hide show
  1. package/dist/browser/modules/archive/create-archive.d.ts +23 -0
  2. package/dist/browser/modules/archive/create-archive.js +13 -0
  3. package/dist/browser/modules/archive/fs/archive-file.d.ts +1 -1
  4. package/dist/browser/modules/archive/fs/types.d.ts +1 -1
  5. package/dist/browser/modules/archive/index.base.d.ts +6 -4
  6. package/dist/browser/modules/archive/index.base.js +5 -5
  7. package/dist/browser/modules/archive/read-archive.d.ts +27 -0
  8. package/dist/browser/modules/archive/read-archive.js +12 -0
  9. package/dist/browser/modules/archive/unzip/index.d.ts +0 -27
  10. package/dist/browser/modules/archive/unzip/index.js +0 -4
  11. package/dist/browser/modules/archive/{io → unzip}/remote-zip-reader.d.ts +1 -1
  12. package/dist/browser/modules/archive/{io → unzip}/remote-zip-reader.js +3 -3
  13. package/dist/browser/modules/archive/unzip/zip-reader.d.ts +1 -2
  14. package/dist/browser/modules/archive/unzip/zip-reader.js +0 -3
  15. package/dist/browser/modules/archive/zip/index.d.ts +0 -24
  16. package/dist/browser/modules/archive/zip/index.js +0 -4
  17. package/dist/browser/modules/archive/zip/zip-archive.d.ts +1 -2
  18. package/dist/browser/modules/archive/zip/zip-archive.js +42 -140
  19. package/dist/browser/modules/archive/zip/zip-editor.js +117 -207
  20. package/dist/browser/modules/archive/zip/zip-output-pipeline.d.ts +55 -0
  21. package/dist/browser/modules/archive/zip/zip-output-pipeline.js +127 -0
  22. package/dist/cjs/modules/archive/create-archive.js +16 -0
  23. package/dist/cjs/modules/archive/index.base.js +7 -9
  24. package/dist/cjs/modules/archive/read-archive.js +15 -0
  25. package/dist/cjs/modules/archive/unzip/index.js +0 -5
  26. package/dist/cjs/modules/archive/{io → unzip}/remote-zip-reader.js +3 -3
  27. package/dist/cjs/modules/archive/unzip/zip-reader.js +0 -4
  28. package/dist/cjs/modules/archive/zip/index.js +0 -5
  29. package/dist/cjs/modules/archive/zip/zip-archive.js +40 -139
  30. package/dist/cjs/modules/archive/zip/zip-editor.js +115 -205
  31. package/dist/cjs/modules/archive/zip/zip-output-pipeline.js +130 -0
  32. package/dist/esm/modules/archive/create-archive.js +13 -0
  33. package/dist/esm/modules/archive/index.base.js +5 -5
  34. package/dist/esm/modules/archive/read-archive.js +12 -0
  35. package/dist/esm/modules/archive/unzip/index.js +0 -4
  36. package/dist/esm/modules/archive/{io → unzip}/remote-zip-reader.js +3 -3
  37. package/dist/esm/modules/archive/unzip/zip-reader.js +0 -3
  38. package/dist/esm/modules/archive/zip/index.js +0 -4
  39. package/dist/esm/modules/archive/zip/zip-archive.js +42 -140
  40. package/dist/esm/modules/archive/zip/zip-editor.js +117 -207
  41. package/dist/esm/modules/archive/zip/zip-output-pipeline.js +127 -0
  42. package/dist/iife/excelts.iife.js +1 -1
  43. package/dist/iife/excelts.iife.min.js +1 -1
  44. package/dist/types/modules/archive/create-archive.d.ts +23 -0
  45. package/dist/types/modules/archive/fs/archive-file.d.ts +1 -1
  46. package/dist/types/modules/archive/fs/types.d.ts +1 -1
  47. package/dist/types/modules/archive/index.base.d.ts +6 -4
  48. package/dist/types/modules/archive/read-archive.d.ts +27 -0
  49. package/dist/types/modules/archive/unzip/index.d.ts +0 -27
  50. package/dist/types/modules/archive/{io → unzip}/remote-zip-reader.d.ts +1 -1
  51. package/dist/types/modules/archive/unzip/zip-reader.d.ts +1 -2
  52. package/dist/types/modules/archive/zip/index.d.ts +0 -24
  53. package/dist/types/modules/archive/zip/zip-archive.d.ts +1 -2
  54. package/dist/types/modules/archive/zip/zip-output-pipeline.d.ts +55 -0
  55. package/package.json +1 -1
  56. package/dist/browser/modules/archive/formats/index.d.ts +0 -9
  57. package/dist/browser/modules/archive/formats/index.js +0 -28
  58. package/dist/cjs/modules/archive/formats/index.js +0 -32
  59. package/dist/esm/modules/archive/formats/index.js +0 -28
  60. package/dist/types/modules/archive/formats/index.d.ts +0 -9
  61. /package/dist/browser/modules/archive/{formats → shared}/types.d.ts +0 -0
  62. /package/dist/browser/modules/archive/{formats → shared}/types.js +0 -0
  63. /package/dist/cjs/modules/archive/{formats → shared}/types.js +0 -0
  64. /package/dist/esm/modules/archive/{formats → shared}/types.js +0 -0
  65. /package/dist/types/modules/archive/{formats → shared}/types.d.ts +0 -0
@@ -2,13 +2,12 @@ import { dateToZipDos } from "../zip-spec/timestamps.js";
2
2
  import { encodeZipStringWithCodec, resolveZipStringCodec } from "../shared/text.js";
3
3
  import { DEFAULT_ZIP_LEVEL, DEFAULT_ZIP_TIMESTAMPS, REPRODUCIBLE_ZIP_MOD_TIME } from "../shared/defaults.js";
4
4
  import { BufferReader, HttpRangeReader } from "../io/random-access.js";
5
- import { RemoteZipReader } from "../io/remote-zip-reader.js";
5
+ import { RemoteZipReader } from "../unzip/remote-zip-reader.js";
6
6
  import { toAsyncIterable, collectUint8ArrayStream, toUint8ArraySync, isSyncArchiveSource, isInMemoryArchiveSource, resolveArchiveSourceToBuffer } from "../io/archive-source.js";
7
7
  import { collect, pipeIterableToSink } from "../io/archive-sink.js";
8
- import { createAsyncQueue } from "../shared/async-queue.js";
9
- import { createLinkedAbortController, createAbortError, throwIfAborted, toError } from "../shared/errors.js";
10
- import { ProgressEmitter } from "../shared/progress.js";
11
- import { ZipDeflateFile, StreamingZip, ZipRawFile } from "./stream.js";
8
+ import { throwIfAborted, toError } from "../shared/errors.js";
9
+ import { createZipOperation } from "./zip-output-pipeline.js";
10
+ import { ZipDeflateFile, ZipRawFile } from "./stream.js";
12
11
  import { createZip, createZipSync } from "./zip-bytes.js";
13
12
  import { FLAG_ENCRYPTED } from "../zip-spec/zip-records.js";
14
13
  import { ZipEditView } from "./zip-edit-view.js";
@@ -360,226 +359,137 @@ export class ZipEditor {
360
359
  const signalOpt = options.signal ?? this._streamDefaults.signal;
361
360
  const onProgress = options.onProgress ?? this._streamDefaults.onProgress;
362
361
  const progressIntervalMs = options.progressIntervalMs ?? this._streamDefaults.progressIntervalMs;
363
- const { controller, cleanup: cleanupAbortLink } = createLinkedAbortController(signalOpt);
364
- const signal = controller.signal;
365
362
  const preservedMeta = this._buildRawPreservedEntries();
366
363
  const sets = this._buildSetEntries();
367
- const preservedRaw = [];
368
- const preservedRecompressed = [];
369
- for (const p of preservedMeta) {
370
- const compressedData = this._remote.getRawCompressedStream(p.info.path, { signal });
371
- if (compressedData) {
372
- preservedRaw.push({ ...p, compressedData });
373
- continue;
374
- }
375
- this._emitWarning(p.info.path, "raw_unavailable", `Cannot read raw compressed payload for entry "${p.info.path}".`);
376
- if (this._options.preserve === "strict") {
377
- throw new Error(`Cannot preserve entry "${p.info.path}" because its raw compressed payload is unavailable.`);
364
+ return createZipOperation(preservedMeta.length + sets.length, {
365
+ comment: this._options.comment,
366
+ zip64: this._options.zip64,
367
+ codec: this._stringCodec
368
+ }, { signal: signalOpt, onProgress, progressIntervalMs }, async ({ zip, signal, progress }) => {
369
+ // Classify preserved entries using the pipeline's linked signal so that
370
+ // abort() properly cancels raw compressed streams.
371
+ const preservedRaw = [];
372
+ const preservedRecompressed = [];
373
+ for (const p of preservedMeta) {
374
+ const compressedData = this._remote.getRawCompressedStream(p.info.path, { signal });
375
+ if (compressedData) {
376
+ preservedRaw.push({ ...p, compressedData });
377
+ continue;
378
+ }
379
+ this._emitWarning(p.info.path, "raw_unavailable", `Cannot read raw compressed payload for entry "${p.info.path}".`);
380
+ if (this._options.preserve === "strict") {
381
+ throw new Error(`Cannot preserve entry "${p.info.path}" because its raw compressed payload is unavailable.`);
382
+ }
383
+ // We cannot re-encrypt entries; best-effort must not silently output decrypted content.
384
+ if (p.info.isEncrypted) {
385
+ this._emitWarning(p.info.path, "encryption_unsupported", `Cannot best-effort preserve encrypted entry "${p.info.path}" without raw passthrough.`);
386
+ continue;
387
+ }
388
+ // Best-effort fallback: extract and re-add the entry.
389
+ preservedRecompressed.push({ name: p.outName, info: p.info });
378
390
  }
379
- // We cannot re-encrypt entries; best-effort must not silently output decrypted content.
380
- if (p.info.isEncrypted) {
381
- this._emitWarning(p.info.path, "encryption_unsupported", `Cannot best-effort preserve encrypted entry "${p.info.path}" without raw passthrough.`);
382
- continue;
391
+ // Update entriesTotal now that we know how many entries survived classification.
392
+ const actualTotal = preservedRaw.length + preservedRecompressed.length + sets.length;
393
+ progress.set("entriesTotal", actualTotal);
394
+ // 1) Preserved entries: passthrough raw payload.
395
+ for (let i = 0; i < preservedRaw.length; i++) {
396
+ throwIfAborted(signal);
397
+ const entry = preservedRaw[i];
398
+ progress.update({ currentEntry: { name: entry.outName, index: i, bytesIn: 0 } });
399
+ const rawFile = this._buildPreservedRawFile(entry.outName, entry.info, entry.compressedData, this._options.zip64);
400
+ zip.add(rawFile);
401
+ // StreamingZip auto-starts passthrough files; await completion for accurate progress.
402
+ await rawFile.done();
403
+ progress.set("entriesDone", progress.snapshot.entriesDone + 1);
383
404
  }
384
- // Best-effort fallback: extract and re-add the entry.
385
- // This may require holding the entry in memory.
386
- preservedRecompressed.push({ name: p.outName, info: p.info });
387
- }
388
- const progress = new ProgressEmitter({
389
- type: "zip",
390
- phase: "running",
391
- entriesTotal: preservedRaw.length + preservedRecompressed.length + sets.length,
392
- entriesDone: 0,
393
- bytesIn: 0,
394
- bytesOut: 0,
395
- zip64: this._options.zip64
396
- }, onProgress, { intervalMs: progressIntervalMs });
397
- const queue = createAsyncQueue({
398
- onCancel: () => {
405
+ // 1b) Best-effort preserved entries: extract and re-add.
406
+ for (let k = 0; k < preservedRecompressed.length; k++) {
407
+ throwIfAborted(signal);
408
+ const idx = preservedRaw.length + k;
409
+ const entry = preservedRecompressed[k];
410
+ let entryBytesIn = 0;
411
+ progress.update({ currentEntry: { name: entry.name, index: idx, bytesIn: 0 } });
412
+ let data;
399
413
  try {
400
- controller.abort("cancelled");
414
+ data = await this._remote.extractEntry(entry.info);
401
415
  }
402
- catch {
403
- // ignore
416
+ catch (e) {
417
+ const err = toError(e);
418
+ this._emitWarning(entry.info.path, "unknown", `Failed to extract entry "${entry.info.path}" for best-effort preserve: ${err.message}`);
419
+ progress.set("entriesDone", progress.snapshot.entriesDone + 1);
420
+ continue;
404
421
  }
405
- }
406
- });
407
- const zip = new StreamingZip((err, data, final) => {
408
- if (err) {
409
- progress.update({ phase: progress.snapshot.phase === "aborted" ? "aborted" : "error" });
410
- queue.fail(err);
411
- return;
412
- }
413
- if (data.length) {
422
+ const fallbackLevel = entry.info.compressionMethod === 0 ? 0 : this._options.level;
423
+ const file = new ZipDeflateFile(entry.name, buildZipDeflateFileOptions({
424
+ level: fallbackLevel,
425
+ modTime: entry.info.lastModified,
426
+ comment: entry.info.comment,
427
+ externalAttributes: entry.info.externalAttributes,
428
+ versionMadeBy: entry.info.versionMadeBy
429
+ }, {
430
+ level: this._options.level,
431
+ modTime: this._options.modTime,
432
+ timestamps: this._options.timestamps,
433
+ smartStore: this._options.smartStore,
434
+ zip64: this._options.zip64,
435
+ path: this._options.path,
436
+ encoding: this._options.encoding
437
+ }));
438
+ zip.add(file);
439
+ entryBytesIn += data.length;
414
440
  progress.mutate(s => {
415
- s.bytesOut += data.length;
441
+ s.bytesIn += data.length;
442
+ s.currentEntry = { name: entry.name, index: idx, bytesIn: entryBytesIn };
416
443
  });
417
- queue.push(data);
418
- }
419
- if (final) {
420
- if (progress.snapshot.phase === "running") {
421
- progress.update({ phase: "done" });
422
- }
423
- queue.close();
444
+ await file.push(data, true);
445
+ await file.complete();
446
+ progress.set("entriesDone", progress.snapshot.entriesDone + 1);
424
447
  }
425
- }, {
426
- comment: this._options.comment,
427
- zip64: this._options.zip64,
428
- codec: this._stringCodec
429
- });
430
- const onAbort = () => {
431
- const err = createAbortError(signal.reason);
432
- progress.update({ phase: "aborted" });
433
- try {
434
- zip.abort(err);
435
- }
436
- catch {
437
- // ignore
438
- }
439
- queue.fail(err);
440
- };
441
- signal.addEventListener("abort", onAbort, { once: true });
442
- (async () => {
443
- try {
444
- // 1) Preserved entries: passthrough raw payload.
445
- for (let i = 0; i < preservedRaw.length; i++) {
446
- throwIfAborted(signal);
447
- const entry = preservedRaw[i];
448
- progress.update({ currentEntry: { name: entry.outName, index: i, bytesIn: 0 } });
449
- const rawFile = this._buildPreservedRawFile(entry.outName, entry.info, entry.compressedData, this._options.zip64);
450
- zip.add(rawFile);
451
- // StreamingZip auto-starts passthrough files; await completion for accurate progress.
452
- await rawFile.done();
453
- progress.set("entriesDone", progress.snapshot.entriesDone + 1);
454
- }
455
- // 1b) Best-effort preserved entries: extract and re-add.
456
- for (let k = 0; k < preservedRecompressed.length; k++) {
457
- throwIfAborted(signal);
458
- const idx = preservedRaw.length + k;
459
- const entry = preservedRecompressed[k];
460
- let entryBytesIn = 0;
461
- progress.update({ currentEntry: { name: entry.name, index: idx, bytesIn: 0 } });
462
- let data;
463
- try {
464
- data = await this._remote.extractEntry(entry.info);
465
- }
466
- catch (e) {
467
- const err = toError(e);
468
- this._emitWarning(entry.info.path, "unknown", `Failed to extract entry "${entry.info.path}" for best-effort preserve: ${err.message}`);
469
- progress.set("entriesDone", progress.snapshot.entriesDone + 1);
470
- continue;
471
- }
472
- const fallbackLevel = entry.info.compressionMethod === 0 ? 0 : this._options.level;
473
- const file = new ZipDeflateFile(entry.name, buildZipDeflateFileOptions({
474
- level: fallbackLevel,
475
- modTime: entry.info.lastModified,
476
- comment: entry.info.comment,
477
- externalAttributes: entry.info.externalAttributes,
478
- versionMadeBy: entry.info.versionMadeBy
479
- }, {
480
- level: this._options.level,
481
- modTime: this._options.modTime,
482
- timestamps: this._options.timestamps,
483
- smartStore: this._options.smartStore,
484
- zip64: this._options.zip64,
485
- path: this._options.path,
486
- encoding: this._options.encoding
487
- }));
488
- zip.add(file);
489
- entryBytesIn += data.length;
448
+ // 2) Set/update entries: compress from source.
449
+ for (let j = 0; j < sets.length; j++) {
450
+ throwIfAborted(signal);
451
+ const idx = preservedRaw.length + preservedRecompressed.length + j;
452
+ const entry = sets[j];
453
+ let entryBytesIn = 0;
454
+ progress.update({ currentEntry: { name: entry.name, index: idx, bytesIn: 0 } });
455
+ const file = new ZipDeflateFile(entry.name, buildZipDeflateFileOptions(entry.options, {
456
+ level: this._options.level,
457
+ modTime: this._options.modTime,
458
+ timestamps: this._options.timestamps,
459
+ smartStore: this._options.smartStore,
460
+ zip64: this._options.zip64,
461
+ path: this._options.path,
462
+ encoding: this._options.encoding
463
+ }));
464
+ zip.add(file);
465
+ const onChunk = (chunk) => {
466
+ entryBytesIn += chunk.length;
490
467
  progress.mutate(s => {
491
- s.bytesIn += data.length;
468
+ s.bytesIn += chunk.length;
492
469
  s.currentEntry = { name: entry.name, index: idx, bytesIn: entryBytesIn };
493
470
  });
494
- await file.push(data, true);
495
- await file.complete();
496
- progress.set("entriesDone", progress.snapshot.entriesDone + 1);
497
- }
498
- // 2) Set/update entries: compress from source.
499
- for (let j = 0; j < sets.length; j++) {
471
+ };
472
+ if (isSyncArchiveSource(entry.source)) {
473
+ const bytes = toUint8ArraySync(entry.source);
500
474
  throwIfAborted(signal);
501
- const idx = preservedRaw.length + preservedRecompressed.length + j;
502
- const entry = sets[j];
503
- let entryBytesIn = 0;
504
- progress.update({ currentEntry: { name: entry.name, index: idx, bytesIn: 0 } });
505
- const file = new ZipDeflateFile(entry.name, buildZipDeflateFileOptions(entry.options, {
506
- level: this._options.level,
507
- modTime: this._options.modTime,
508
- timestamps: this._options.timestamps,
509
- smartStore: this._options.smartStore,
510
- zip64: this._options.zip64,
511
- path: this._options.path,
512
- encoding: this._options.encoding
513
- }));
514
- zip.add(file);
515
- const onChunk = (chunk) => {
516
- entryBytesIn += chunk.length;
517
- progress.mutate(s => {
518
- s.bytesIn += chunk.length;
519
- s.currentEntry = { name: entry.name, index: idx, bytesIn: entryBytesIn };
520
- });
521
- };
522
- if (isSyncArchiveSource(entry.source)) {
523
- const bytes = toUint8ArraySync(entry.source);
524
- throwIfAborted(signal);
525
- onChunk(bytes);
526
- await file.push(bytes, true);
527
- }
528
- else {
529
- // Streaming path (includes Blob via toAsyncIterable(Blob) which prefers Blob.stream())
530
- for await (const chunk of toAsyncIterable(entry.source, { signal, onChunk })) {
531
- throwIfAborted(signal);
532
- await file.push(chunk, false);
533
- }
534
- throwIfAborted(signal);
535
- await file.push(new Uint8Array(0), true);
536
- }
537
- await file.complete();
538
- progress.set("entriesDone", progress.snapshot.entriesDone + 1);
539
- }
540
- throwIfAborted(signal);
541
- zip.end();
542
- }
543
- catch (e) {
544
- const err = toError(e);
545
- if (err.name === "AbortError") {
546
- progress.update({ phase: "aborted" });
547
- try {
548
- zip.abort(err);
549
- }
550
- catch {
551
- // ignore
552
- }
475
+ onChunk(bytes);
476
+ await file.push(bytes, true);
553
477
  }
554
478
  else {
555
- progress.update({ phase: "error" });
556
- }
557
- queue.fail(err);
558
- }
559
- finally {
560
- try {
561
- signal.removeEventListener("abort", onAbort);
562
- }
563
- catch {
564
- // ignore
479
+ // Streaming path (includes Blob via toAsyncIterable(Blob) which prefers Blob.stream())
480
+ for await (const chunk of toAsyncIterable(entry.source, { signal, onChunk })) {
481
+ throwIfAborted(signal);
482
+ await file.push(chunk, false);
483
+ }
484
+ throwIfAborted(signal);
485
+ await file.push(new Uint8Array(0), true);
565
486
  }
566
- cleanupAbortLink();
567
- progress.emitNow();
487
+ await file.complete();
488
+ progress.set("entriesDone", progress.snapshot.entriesDone + 1);
568
489
  }
569
- })();
570
- return {
571
- iterable: queue.iterable,
572
- signal,
573
- abort(reason) {
574
- controller.abort(reason);
575
- },
576
- pointer() {
577
- return progress.snapshot.bytesOut;
578
- },
579
- progress() {
580
- return progress.snapshotCopy();
581
- }
582
- };
490
+ throwIfAborted(signal);
491
+ zip.end();
492
+ });
583
493
  }
584
494
  /**
585
495
  * Get the output as a single Uint8Array.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared output pipeline for ZIP streaming operations.
3
+ *
4
+ * Both `ZipArchive` and `ZipEditor` use an identical scaffolding pattern for
5
+ * their `operation()` method: signal/abort wiring, ProgressEmitter, async queue,
6
+ * StreamingZip callback, error handling, and return object construction.
7
+ * This module extracts that shared boilerplate into a single reusable function.
8
+ */
9
+ import { StreamingZip } from "./stream.js";
10
+ import { ProgressEmitter } from "../shared/progress.js";
11
+ import type { ZipStringCodec, ZipStringEncoding } from "../shared/text.js";
12
+ import type { Zip64Mode } from "../zip-spec/zip-records.js";
13
+ import type { ZipOperation, ZipProgress, ZipStreamOptions } from "./progress.js";
14
+ /** Resolved signal/progress options passed to the pipeline. */
15
+ export interface ZipPipelineOptions {
16
+ signal?: AbortSignal;
17
+ onProgress?: ZipStreamOptions["onProgress"];
18
+ progressIntervalMs?: number;
19
+ }
20
+ /** Options for constructing the internal StreamingZip instance. */
21
+ export interface ZipPipelineZipOptions {
22
+ comment?: string;
23
+ zip64?: Zip64Mode;
24
+ encoding?: ZipStringEncoding;
25
+ codec?: ZipStringCodec;
26
+ }
27
+ /**
28
+ * Callback that processes entries within the pipeline.
29
+ *
30
+ * The pipeline sets up all scaffolding (abort, progress, queue, StreamingZip)
31
+ * and then calls this function to do the actual per-entry work. The callback
32
+ * receives:
33
+ *
34
+ * - `zip` — the StreamingZip instance to add files to
35
+ * - `signal` — the linked AbortSignal
36
+ * - `progress` — the ProgressEmitter to update
37
+ *
38
+ * The callback MUST call `zip.end()` when all entries have been added.
39
+ * It SHOULD call `throwIfAborted(signal)` between entries.
40
+ */
41
+ export type ZipPipelineProcessFn = (ctx: {
42
+ zip: StreamingZip;
43
+ signal: AbortSignal;
44
+ progress: ProgressEmitter<ZipProgress>;
45
+ }) => Promise<void>;
46
+ /**
47
+ * Create a streaming ZIP operation with all shared boilerplate wired up.
48
+ *
49
+ * @param entriesTotal - Total number of entries (for progress reporting)
50
+ * @param zipOptions - Options for the StreamingZip instance
51
+ * @param pipelineOpts - Signal, progress callback, and interval
52
+ * @param processFn - Callback that adds entries to the StreamingZip
53
+ * @returns A `ZipOperation` handle
54
+ */
55
+ export declare function createZipOperation(entriesTotal: number, zipOptions: ZipPipelineZipOptions, pipelineOpts: ZipPipelineOptions, processFn: ZipPipelineProcessFn): ZipOperation;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Shared output pipeline for ZIP streaming operations.
3
+ *
4
+ * Both `ZipArchive` and `ZipEditor` use an identical scaffolding pattern for
5
+ * their `operation()` method: signal/abort wiring, ProgressEmitter, async queue,
6
+ * StreamingZip callback, error handling, and return object construction.
7
+ * This module extracts that shared boilerplate into a single reusable function.
8
+ */
9
+ import { StreamingZip } from "./stream.js";
10
+ import { createAsyncQueue } from "../shared/async-queue.js";
11
+ import { createLinkedAbortController, createAbortError, toError } from "../shared/errors.js";
12
+ import { ProgressEmitter } from "../shared/progress.js";
13
+ // =============================================================================
14
+ // Pipeline
15
+ // =============================================================================
16
+ /**
17
+ * Create a streaming ZIP operation with all shared boilerplate wired up.
18
+ *
19
+ * @param entriesTotal - Total number of entries (for progress reporting)
20
+ * @param zipOptions - Options for the StreamingZip instance
21
+ * @param pipelineOpts - Signal, progress callback, and interval
22
+ * @param processFn - Callback that adds entries to the StreamingZip
23
+ * @returns A `ZipOperation` handle
24
+ */
25
+ export function createZipOperation(entriesTotal, zipOptions, pipelineOpts, processFn) {
26
+ const { controller, cleanup: cleanupAbortLink } = createLinkedAbortController(pipelineOpts.signal);
27
+ const signal = controller.signal;
28
+ const progress = new ProgressEmitter({
29
+ type: "zip",
30
+ phase: "running",
31
+ entriesTotal,
32
+ entriesDone: 0,
33
+ bytesIn: 0,
34
+ bytesOut: 0,
35
+ zip64: zipOptions.zip64 ?? "auto"
36
+ }, pipelineOpts.onProgress, { intervalMs: pipelineOpts.progressIntervalMs });
37
+ const queue = createAsyncQueue({
38
+ onCancel: () => {
39
+ try {
40
+ controller.abort("cancelled");
41
+ }
42
+ catch {
43
+ // ignore
44
+ }
45
+ }
46
+ });
47
+ const zip = new StreamingZip((err, data, final) => {
48
+ if (err) {
49
+ progress.update({ phase: progress.snapshot.phase === "aborted" ? "aborted" : "error" });
50
+ queue.fail(err);
51
+ return;
52
+ }
53
+ if (data.length) {
54
+ progress.mutate(s => {
55
+ s.bytesOut += data.length;
56
+ });
57
+ queue.push(data);
58
+ }
59
+ if (final) {
60
+ if (progress.snapshot.phase === "running") {
61
+ progress.update({ phase: "done" });
62
+ }
63
+ queue.close();
64
+ }
65
+ }, {
66
+ comment: zipOptions.comment,
67
+ zip64: zipOptions.zip64,
68
+ encoding: zipOptions.encoding,
69
+ codec: zipOptions.codec
70
+ });
71
+ const onAbort = () => {
72
+ const err = createAbortError(signal.reason);
73
+ progress.update({ phase: "aborted" });
74
+ try {
75
+ zip.abort(err);
76
+ }
77
+ catch {
78
+ // ignore
79
+ }
80
+ queue.fail(err);
81
+ };
82
+ signal.addEventListener("abort", onAbort, { once: true });
83
+ (async () => {
84
+ try {
85
+ await processFn({ zip, signal, progress });
86
+ }
87
+ catch (e) {
88
+ const err = toError(e);
89
+ if (err.name === "AbortError") {
90
+ progress.update({ phase: "aborted" });
91
+ try {
92
+ zip.abort(err);
93
+ }
94
+ catch {
95
+ // ignore
96
+ }
97
+ }
98
+ else {
99
+ progress.update({ phase: "error" });
100
+ }
101
+ queue.fail(err);
102
+ }
103
+ finally {
104
+ try {
105
+ signal.removeEventListener("abort", onAbort);
106
+ }
107
+ catch {
108
+ // ignore
109
+ }
110
+ cleanupAbortLink();
111
+ progress.emitNow();
112
+ }
113
+ })();
114
+ return {
115
+ iterable: queue.iterable,
116
+ signal,
117
+ abort(reason) {
118
+ controller.abort(reason);
119
+ },
120
+ pointer() {
121
+ return progress.snapshot.bytesOut;
122
+ },
123
+ progress() {
124
+ return progress.snapshotCopy();
125
+ }
126
+ };
127
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.zip = zip;
4
+ const tar_archive_1 = require("./tar/tar-archive.js");
5
+ const zip_archive_1 = require("./zip/zip-archive.js");
6
+ function zip(options = {}) {
7
+ if (options.format === "tar") {
8
+ return new tar_archive_1.TarArchive({
9
+ modTime: options.modTime,
10
+ signal: options.signal,
11
+ onProgress: options.onProgress,
12
+ progressIntervalMs: options.progressIntervalMs
13
+ });
14
+ }
15
+ return new zip_archive_1.ZipArchive({ ...options, format: "zip" });
16
+ }
@@ -7,8 +7,8 @@
7
7
  * from this file and then layer their platform-specific bindings.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.base64ToUint8Array = exports.isTarDataEntry = exports.isTarHardLink = exports.isTarSymlink = exports.isTarDirectory = exports.isTarFile = exports.untar = exports.parseTarStream = exports.parseTar = exports.tarSync = exports.tar = exports.createTarReader = exports.createTarArchive = exports.TarReaderEntry = exports.TarReader = exports.TarArchive = exports.TAR_TYPE = exports.TAR_BLOCK_SIZE = exports.createReader = exports.createArchive = exports.UnzipEntry = exports.ZipReader = exports.unzip = exports.ZipEditPlan = exports.editZipUrl = exports.editZip = exports.ZipEditor = exports.ZipArchive = exports.zip = exports.EntrySizeMismatchError = exports.UnsupportedCompressionError = exports.FileTooLargeError = exports.PasswordRequiredError = exports.DecryptionError = exports.EocdNotFoundError = exports.InvalidZipSignatureError = exports.ZipParseError = exports.ArchiveError = exports.throwIfAborted = exports.isAbortError = exports.createAbortError = exports.AbortError = exports.Crc32MismatchError = exports.RemoteZipReader = exports.HttpRangeError = exports.RangeNotSupportedError = exports.BufferReader = exports.HttpRangeReader = exports.toReadableStream = exports.toAsyncIterable = void 0;
11
- exports.encryptionMethodFromAesKeyStrength = exports.getAesKeyStrength = exports.isAesEncryption = exports.getEncryptionMethodName = exports.randomBytes = exports.buildAesExtraField = exports.aesEncryptedSize = exports.aesEncrypt = exports.aesDecrypt = exports.COMPRESSION_METHOD_AES = exports.AES_PASSWORD_VERIFY_LENGTH = exports.AES_AUTH_CODE_LENGTH = exports.AES_KEY_LENGTH = exports.AES_SALT_LENGTH = exports.AES_EXTRA_FIELD_ID = exports.AES_VERSION_AE2 = exports.AES_VERSION_AE1 = exports.AES_VENDOR_ID = exports.zipCryptoEncrypt = exports.zipCryptoDecrypt = exports.zipCryptoInitKeys = exports.ZIP_CRYPTO_HEADER_SIZE = exports.uint8ArrayToString = exports.stringToUint8Array = exports.concatUint8Arrays = exports.uint8ArrayToBase64 = void 0;
10
+ exports.concatUint8Arrays = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.isTarDataEntry = exports.isTarHardLink = exports.isTarSymlink = exports.isTarDirectory = exports.isTarFile = exports.untar = exports.parseTarStream = exports.parseTar = exports.tarSync = exports.tar = exports.createTarReader = exports.createTarArchive = exports.TarReaderEntry = exports.TarReader = exports.TarArchive = exports.TAR_TYPE = exports.TAR_BLOCK_SIZE = exports.UnzipEntry = exports.ZipReader = exports.ZipEditPlan = exports.editZipUrl = exports.editZip = exports.ZipEditor = exports.ZipArchive = exports.unzip = exports.zip = exports.EntrySizeMismatchError = exports.UnsupportedCompressionError = exports.FileTooLargeError = exports.PasswordRequiredError = exports.DecryptionError = exports.EocdNotFoundError = exports.InvalidZipSignatureError = exports.ZipParseError = exports.ArchiveError = exports.throwIfAborted = exports.isAbortError = exports.createAbortError = exports.AbortError = exports.Crc32MismatchError = exports.RemoteZipReader = exports.HttpRangeError = exports.RangeNotSupportedError = exports.BufferReader = exports.HttpRangeReader = exports.toReadableStream = exports.toAsyncIterable = void 0;
11
+ exports.encryptionMethodFromAesKeyStrength = exports.getAesKeyStrength = exports.isAesEncryption = exports.getEncryptionMethodName = exports.randomBytes = exports.buildAesExtraField = exports.aesEncryptedSize = exports.aesEncrypt = exports.aesDecrypt = exports.COMPRESSION_METHOD_AES = exports.AES_PASSWORD_VERIFY_LENGTH = exports.AES_AUTH_CODE_LENGTH = exports.AES_KEY_LENGTH = exports.AES_SALT_LENGTH = exports.AES_EXTRA_FIELD_ID = exports.AES_VERSION_AE2 = exports.AES_VERSION_AE1 = exports.AES_VENDOR_ID = exports.zipCryptoEncrypt = exports.zipCryptoDecrypt = exports.zipCryptoInitKeys = exports.ZIP_CRYPTO_HEADER_SIZE = exports.uint8ArrayToString = exports.stringToUint8Array = void 0;
12
12
  var archive_source_1 = require("./io/archive-source.js");
13
13
  Object.defineProperty(exports, "toAsyncIterable", { enumerable: true, get: function () { return archive_source_1.toAsyncIterable; } });
14
14
  Object.defineProperty(exports, "toReadableStream", { enumerable: true, get: function () { return archive_source_1.toReadableStream; } });
@@ -18,7 +18,7 @@ Object.defineProperty(exports, "HttpRangeReader", { enumerable: true, get: funct
18
18
  Object.defineProperty(exports, "BufferReader", { enumerable: true, get: function () { return random_access_1.BufferReader; } });
19
19
  Object.defineProperty(exports, "RangeNotSupportedError", { enumerable: true, get: function () { return random_access_1.RangeNotSupportedError; } });
20
20
  Object.defineProperty(exports, "HttpRangeError", { enumerable: true, get: function () { return random_access_1.HttpRangeError; } });
21
- var remote_zip_reader_1 = require("./io/remote-zip-reader.js");
21
+ var remote_zip_reader_1 = require("./unzip/remote-zip-reader.js");
22
22
  Object.defineProperty(exports, "RemoteZipReader", { enumerable: true, get: function () { return remote_zip_reader_1.RemoteZipReader; } });
23
23
  Object.defineProperty(exports, "Crc32MismatchError", { enumerable: true, get: function () { return remote_zip_reader_1.Crc32MismatchError; } });
24
24
  // Abort and Error types - all from centralized errors module
@@ -39,21 +39,19 @@ Object.defineProperty(exports, "FileTooLargeError", { enumerable: true, get: fun
39
39
  Object.defineProperty(exports, "UnsupportedCompressionError", { enumerable: true, get: function () { return errors_1.UnsupportedCompressionError; } });
40
40
  Object.defineProperty(exports, "EntrySizeMismatchError", { enumerable: true, get: function () { return errors_1.EntrySizeMismatchError; } });
41
41
  // High-level APIs
42
+ var create_archive_1 = require("./create-archive.js");
43
+ Object.defineProperty(exports, "zip", { enumerable: true, get: function () { return create_archive_1.zip; } });
44
+ var read_archive_1 = require("./read-archive.js");
45
+ Object.defineProperty(exports, "unzip", { enumerable: true, get: function () { return read_archive_1.unzip; } });
42
46
  var zip_1 = require("./zip/index.js");
43
- Object.defineProperty(exports, "zip", { enumerable: true, get: function () { return zip_1.zip; } });
44
47
  Object.defineProperty(exports, "ZipArchive", { enumerable: true, get: function () { return zip_1.ZipArchive; } });
45
48
  Object.defineProperty(exports, "ZipEditor", { enumerable: true, get: function () { return zip_1.ZipEditor; } });
46
49
  Object.defineProperty(exports, "editZip", { enumerable: true, get: function () { return zip_1.editZip; } });
47
50
  Object.defineProperty(exports, "editZipUrl", { enumerable: true, get: function () { return zip_1.editZipUrl; } });
48
51
  Object.defineProperty(exports, "ZipEditPlan", { enumerable: true, get: function () { return zip_1.ZipEditPlan; } });
49
52
  var unzip_1 = require("./unzip/index.js");
50
- Object.defineProperty(exports, "unzip", { enumerable: true, get: function () { return unzip_1.unzip; } });
51
53
  Object.defineProperty(exports, "ZipReader", { enumerable: true, get: function () { return unzip_1.ZipReader; } });
52
54
  Object.defineProperty(exports, "UnzipEntry", { enumerable: true, get: function () { return unzip_1.UnzipEntry; } });
53
- // Format registry (ZIP/TAR dispatch)
54
- var formats_1 = require("./formats/index.js");
55
- Object.defineProperty(exports, "createArchive", { enumerable: true, get: function () { return formats_1.createArchive; } });
56
- Object.defineProperty(exports, "createReader", { enumerable: true, get: function () { return formats_1.createReader; } });
57
55
  // TAR archive support (unified API compatible with ZIP)
58
56
  // Note: Gzip helpers are exported separately in index.ts / index.browser.ts
59
57
  var index_browser_1 = require("./tar/index.browser.js");
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unzip = unzip;
4
+ const tar_archive_1 = require("./tar/tar-archive.js");
5
+ const zip_reader_1 = require("./unzip/zip-reader.js");
6
+ function unzip(source, options = {}) {
7
+ if (options.format === "tar") {
8
+ return new tar_archive_1.TarReader(source, {
9
+ signal: options.signal,
10
+ onProgress: options.onProgress,
11
+ progressIntervalMs: options.progressIntervalMs
12
+ });
13
+ }
14
+ return new zip_reader_1.ZipReader(source, { ...options, format: "zip" });
15
+ }
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZipReader = exports.UnzipEntry = void 0;
4
- exports.unzip = unzip;
5
- const formats_1 = require("../formats/index.js");
6
4
  var zip_reader_1 = require("./zip-reader");
7
5
  Object.defineProperty(exports, "UnzipEntry", { enumerable: true, get: function () { return zip_reader_1.UnzipEntry; } });
8
6
  Object.defineProperty(exports, "ZipReader", { enumerable: true, get: function () { return zip_reader_1.ZipReader; } });
9
- function unzip(source, options) {
10
- return (0, formats_1.createReader)(source, options);
11
- }