neozipkit 0.7.2 → 1.0.2

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 (70) hide show
  1. package/README.md +57 -24
  2. package/WHATS_NEW.md +114 -0
  3. package/dist/browser/zstd-browser-shim.d.ts +28 -0
  4. package/dist/browser/zstd-browser-shim.d.ts.map +1 -0
  5. package/dist/browser/zstd-browser-shim.js +66 -0
  6. package/dist/browser/zstd-browser-shim.js.map +1 -0
  7. package/dist/browser-esm/index.js +1786 -41737
  8. package/dist/browser-esm/index.js.map +4 -4
  9. package/dist/browser-umd/index.js +1786 -41737
  10. package/dist/browser-umd/index.js.map +4 -4
  11. package/dist/browser-umd/index.min.js +3 -21
  12. package/dist/core/ZipCompress.d.ts +2 -7
  13. package/dist/core/ZipCompress.d.ts.map +1 -1
  14. package/dist/core/ZipCompress.js +45 -22
  15. package/dist/core/ZipCompress.js.map +1 -1
  16. package/dist/core/ZipDecompress.d.ts +1 -3
  17. package/dist/core/ZipDecompress.d.ts.map +1 -1
  18. package/dist/core/ZipDecompress.js +14 -9
  19. package/dist/core/ZipDecompress.js.map +1 -1
  20. package/dist/core/encryption/AesCrypto.d.ts +55 -11
  21. package/dist/core/encryption/AesCrypto.d.ts.map +1 -1
  22. package/dist/core/encryption/AesCrypto.js +223 -68
  23. package/dist/core/encryption/AesCrypto.js.map +1 -1
  24. package/dist/core/encryption/index.d.ts +1 -1
  25. package/dist/core/encryption/index.d.ts.map +1 -1
  26. package/dist/core/encryption/index.js +3 -1
  27. package/dist/core/encryption/index.js.map +1 -1
  28. package/dist/core/version.js +1 -1
  29. package/dist/node/LegacyZstd.d.ts +67 -0
  30. package/dist/node/LegacyZstd.d.ts.map +1 -0
  31. package/dist/node/LegacyZstd.js +209 -0
  32. package/dist/node/LegacyZstd.js.map +1 -0
  33. package/dist/node/ZipCompressNode.d.ts +9 -7
  34. package/dist/node/ZipCompressNode.d.ts.map +1 -1
  35. package/dist/node/ZipCompressNode.js +85 -46
  36. package/dist/node/ZipCompressNode.js.map +1 -1
  37. package/dist/node/ZipDecompressNode.d.ts +7 -3
  38. package/dist/node/ZipDecompressNode.d.ts.map +1 -1
  39. package/dist/node/ZipDecompressNode.js +51 -55
  40. package/dist/node/ZipDecompressNode.js.map +1 -1
  41. package/dist/node/ZipkitNode.d.ts +14 -0
  42. package/dist/node/ZipkitNode.d.ts.map +1 -1
  43. package/dist/node/ZipkitNode.js +130 -39
  44. package/dist/node/ZipkitNode.js.map +1 -1
  45. package/dist/node/ZstdNode.d.ts +39 -0
  46. package/dist/node/ZstdNode.d.ts.map +1 -0
  47. package/dist/node/ZstdNode.js +171 -0
  48. package/dist/node/ZstdNode.js.map +1 -0
  49. package/dist/node/index.d.ts +3 -0
  50. package/dist/node/index.d.ts.map +1 -1
  51. package/dist/node/index.js +11 -1
  52. package/dist/node/index.js.map +1 -1
  53. package/package.json +10 -10
  54. package/src/browser/zstd-browser-shim.ts +76 -0
  55. package/src/core/ZipCompress.ts +57 -29
  56. package/src/core/ZipDecompress.ts +21 -10
  57. package/src/core/encryption/AesCrypto.ts +265 -86
  58. package/src/core/encryption/index.ts +1 -1
  59. package/src/core/version.ts +1 -1
  60. package/src/node/LegacyZstd.ts +270 -0
  61. package/src/node/ZipCompressNode.ts +107 -55
  62. package/src/node/ZipDecompressNode.ts +80 -66
  63. package/src/node/ZipkitNode.ts +150 -40
  64. package/src/node/ZstdNode.ts +155 -0
  65. package/src/node/index.ts +10 -0
  66. package/dist/core/ZstdManager.d.ts +0 -56
  67. package/dist/core/ZstdManager.d.ts.map +0 -1
  68. package/dist/core/ZstdManager.js +0 -144
  69. package/dist/core/ZstdManager.js.map +0 -1
  70. package/src/core/ZstdManager.ts +0 -165
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Detect legacy NeoZipKit WASM zstd (`@oneidentity/zstd-js`).
3
+ *
4
+ * That codec wrote valid-looking zstd frames but declared Frame_Content_Size as
5
+ * `uncompressed_size + 18`. Standard libzstd / Node zlib decode to the real
6
+ * payload plus 18 trailing zero bytes. CRC of the truncated payload matches
7
+ * the ZIP entry CRC.
8
+ *
9
+ * Header-only checks do not require decompressing. Verified checks decompress
10
+ * with native zlib and confirm trailing zeros + optional CRC.
11
+ */
12
+
13
+ import { ZstdNode } from './ZstdNode';
14
+ import { HashCalculator } from '../core/components/HashCalculator';
15
+
16
+ /** Fixed extra bytes declared/emitted by the broken WASM codec. */
17
+ export const LEGACY_WASM_ZSTD_PADDING = 18;
18
+
19
+ const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
20
+
21
+ export type LegacyZstdConfidence = 'none' | 'header' | 'verified';
22
+
23
+ export interface LegacyZstdDetectResult {
24
+ /** True when this looks like (or verifies as) legacy WASM zstd. */
25
+ isLegacyWasm: boolean;
26
+ /** How the decision was made. */
27
+ confidence: LegacyZstdConfidence;
28
+ /** Parsed Frame_Content_Size, if present in the frame header. */
29
+ frameContentSize: number | null;
30
+ /** Expected plaintext size from the ZIP entry (or caller). */
31
+ expectedUncompressedSize: number;
32
+ /** Declared/observed padding past expected size (typically 18). */
33
+ paddingBytes: number;
34
+ /** Human-readable reason. */
35
+ reason: string;
36
+ }
37
+
38
+ export interface DetectLegacyZstdOptions {
39
+ /**
40
+ * When true (default), decompress with native zlib and confirm
41
+ * trailing zeros (+ optional CRC) instead of trusting the header alone.
42
+ */
43
+ verify?: boolean;
44
+ /** ZIP CRC-32 of the original uncompressed data (strengthens verified check). */
45
+ crc?: number;
46
+ }
47
+
48
+ /**
49
+ * Read Frame_Content_Size from a zstd frame header, or null if absent / unparseable.
50
+ * @see https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md
51
+ */
52
+ export function parseZstdFrameContentSize(compressed: Buffer): number | null {
53
+ if (!compressed || compressed.length < 6) {
54
+ return null;
55
+ }
56
+ if (
57
+ compressed[0] !== ZSTD_MAGIC[0] ||
58
+ compressed[1] !== ZSTD_MAGIC[1] ||
59
+ compressed[2] !== ZSTD_MAGIC[2] ||
60
+ compressed[3] !== ZSTD_MAGIC[3]
61
+ ) {
62
+ return null;
63
+ }
64
+
65
+ const descriptor = compressed[4];
66
+ const fcsFlag = (descriptor >> 6) & 0x3;
67
+ const singleSegment = ((descriptor >> 5) & 0x1) === 1;
68
+ const dictIdFlag = descriptor & 0x3;
69
+ const dictIdSizes = [0, 1, 2, 4];
70
+ let pos = 5 + dictIdSizes[dictIdFlag];
71
+
72
+ if (fcsFlag === 0) {
73
+ if (!singleSegment) {
74
+ return null;
75
+ }
76
+ if (pos >= compressed.length) {
77
+ return null;
78
+ }
79
+ return compressed[pos];
80
+ }
81
+
82
+ const fcsSizes = [0, 2, 4, 8];
83
+ const n = fcsSizes[fcsFlag];
84
+ if (pos + n > compressed.length) {
85
+ return null;
86
+ }
87
+
88
+ let fcs: number;
89
+ if (n === 2) {
90
+ fcs = compressed.readUInt16LE(pos);
91
+ } else if (n === 4) {
92
+ fcs = compressed.readUInt32LE(pos);
93
+ } else {
94
+ const lo = compressed.readUInt32LE(pos);
95
+ const hi = compressed.readUInt32LE(pos + 4);
96
+ // Content sizes that do not fit in JS safe integers are not our legacy case.
97
+ if (hi !== 0) {
98
+ return null;
99
+ }
100
+ fcs = lo;
101
+ }
102
+
103
+ // Spec: when FCS_Flag == 1, stored value is (size - 256).
104
+ if (fcsFlag === 1) {
105
+ fcs += 256;
106
+ }
107
+ return fcs;
108
+ }
109
+
110
+ /**
111
+ * Fast header heuristic: FCS === expectedUncompressedSize + 18.
112
+ */
113
+ export function looksLikeLegacyWasmZstd(
114
+ compressed: Buffer,
115
+ expectedUncompressedSize: number
116
+ ): boolean {
117
+ if (expectedUncompressedSize < 0 || !Number.isFinite(expectedUncompressedSize)) {
118
+ return false;
119
+ }
120
+ const fcs = parseZstdFrameContentSize(compressed);
121
+ if (fcs === null) {
122
+ return false;
123
+ }
124
+ return fcs === expectedUncompressedSize + LEGACY_WASM_ZSTD_PADDING;
125
+ }
126
+
127
+ function crc32Of(data: Buffer): number {
128
+ const h = new HashCalculator();
129
+ h.update(data);
130
+ return h.finalizeCRC32();
131
+ }
132
+
133
+ /**
134
+ * Detect legacy WASM zstd frames for a single compressed payload.
135
+ */
136
+ export async function detectLegacyWasmZstd(
137
+ compressed: Buffer,
138
+ expectedUncompressedSize: number,
139
+ options?: DetectLegacyZstdOptions
140
+ ): Promise<LegacyZstdDetectResult> {
141
+ const expected = expectedUncompressedSize >>> 0;
142
+ const fcs = parseZstdFrameContentSize(compressed);
143
+ const headerMatch =
144
+ fcs !== null && fcs === expected + LEGACY_WASM_ZSTD_PADDING;
145
+
146
+ const base = {
147
+ frameContentSize: fcs,
148
+ expectedUncompressedSize: expected,
149
+ paddingBytes: LEGACY_WASM_ZSTD_PADDING,
150
+ };
151
+
152
+ if (!headerMatch && options?.verify === false) {
153
+ return {
154
+ ...base,
155
+ isLegacyWasm: false,
156
+ confidence: 'none',
157
+ reason:
158
+ fcs === null
159
+ ? 'No Frame_Content_Size in zstd header (or not a zstd frame)'
160
+ : `Frame_Content_Size ${fcs} does not equal expected ${expected} + ${LEGACY_WASM_ZSTD_PADDING}`,
161
+ };
162
+ }
163
+
164
+ if (options?.verify === false) {
165
+ return {
166
+ ...base,
167
+ isLegacyWasm: true,
168
+ confidence: 'header',
169
+ reason: `Frame_Content_Size is expected+${LEGACY_WASM_ZSTD_PADDING} (legacy WASM heuristic)`,
170
+ };
171
+ }
172
+
173
+ // Verified path (default): decompress and check padding / CRC.
174
+ let decoded: Buffer;
175
+ try {
176
+ decoded = await ZstdNode.decompress(compressed);
177
+ } catch (e) {
178
+ return {
179
+ ...base,
180
+ isLegacyWasm: false,
181
+ confidence: 'none',
182
+ reason: `Native decompress failed: ${e instanceof Error ? e.message : String(e)}`,
183
+ };
184
+ }
185
+
186
+ const pad = decoded.length - expected;
187
+ const trailingZeros =
188
+ pad === LEGACY_WASM_ZSTD_PADDING &&
189
+ decoded.subarray(expected).equals(Buffer.alloc(LEGACY_WASM_ZSTD_PADDING));
190
+ const truncated = decoded.subarray(0, Math.min(expected, decoded.length));
191
+ const crcOk =
192
+ options?.crc === undefined ||
193
+ options.crc === 0 ||
194
+ crc32Of(truncated) === (options.crc >>> 0);
195
+
196
+ if (trailingZeros && crcOk && decoded.length === expected + LEGACY_WASM_ZSTD_PADDING) {
197
+ return {
198
+ ...base,
199
+ isLegacyWasm: true,
200
+ confidence: 'verified',
201
+ reason:
202
+ options?.crc && options.crc !== 0
203
+ ? `Decoded length is +${LEGACY_WASM_ZSTD_PADDING} with trailing zeros; truncated CRC matches`
204
+ : `Decoded length is +${LEGACY_WASM_ZSTD_PADDING} with trailing zeros`,
205
+ };
206
+ }
207
+
208
+ if (headerMatch && !trailingZeros) {
209
+ return {
210
+ ...base,
211
+ isLegacyWasm: false,
212
+ confidence: 'none',
213
+ reason:
214
+ 'Header suggested legacy WASM but decoded payload did not have 18 trailing zeros',
215
+ };
216
+ }
217
+
218
+ return {
219
+ ...base,
220
+ isLegacyWasm: false,
221
+ confidence: 'none',
222
+ paddingBytes: Math.max(0, pad),
223
+ reason:
224
+ decoded.length === expected
225
+ ? 'Standard zstd frame (decoded size matches expected)'
226
+ : `Decoded size ${decoded.length} does not match legacy pattern (expected ${expected} or ${expected + LEGACY_WASM_ZSTD_PADDING})`,
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Truncate plaintext if it matches the legacy +18 zero padding pattern.
232
+ * Returns the original buffer when not legacy-shaped.
233
+ */
234
+ export function repairLegacyWasmZstdPlaintext(
235
+ decoded: Buffer,
236
+ expectedUncompressedSize: number,
237
+ crc?: number
238
+ ): { repaired: Buffer; wasLegacy: boolean } {
239
+ const expected = expectedUncompressedSize >>> 0;
240
+ if (
241
+ decoded.length === expected + LEGACY_WASM_ZSTD_PADDING &&
242
+ decoded.subarray(expected).equals(Buffer.alloc(LEGACY_WASM_ZSTD_PADDING))
243
+ ) {
244
+ const repaired = decoded.subarray(0, expected);
245
+ if (crc !== undefined && crc !== 0) {
246
+ if (crc32Of(repaired) !== (crc >>> 0)) {
247
+ return { repaired: decoded, wasLegacy: false };
248
+ }
249
+ }
250
+ return { repaired, wasLegacy: true };
251
+ }
252
+ if (decoded.length > expected && expected > 0) {
253
+ // Match Rust extract behavior: bound to declared uncompressed size for zstd.
254
+ const repaired = decoded.subarray(0, expected);
255
+ if (crc !== undefined && crc !== 0 && crc32Of(repaired) === (crc >>> 0)) {
256
+ return { repaired, wasLegacy: true };
257
+ }
258
+ }
259
+ return { repaired: decoded, wasLegacy: false };
260
+ }
261
+
262
+ export const LegacyZstd = {
263
+ LEGACY_WASM_ZSTD_PADDING,
264
+ parseZstdFrameContentSize,
265
+ looksLikeLegacyWasmZstd,
266
+ detectLegacyWasmZstd,
267
+ repairLegacyWasmZstdPlaintext,
268
+ };
269
+
270
+ export default LegacyZstd;
@@ -22,10 +22,10 @@ import { HashCalculator } from '../core/components/HashCalculator';
22
22
  import { ZipCrypto } from '../core/encryption/ZipCrypto';
23
23
  import { AesCrypto } from '../core/encryption/AesCrypto';
24
24
  import { NeoCrypto, NEO_CRYPTO_ALGORITHM_AES256_V1 } from '../core/encryption/NeoCrypto';
25
- import { ZstdManager } from '../core/ZstdManager';
26
25
  import Errors from '../core/constants/Errors';
27
26
  import * as fs from 'fs';
28
27
  import * as path from 'path';
28
+ import { ZstdNode } from './ZstdNode';
29
29
 
30
30
  const pako = require('pako');
31
31
 
@@ -339,16 +339,23 @@ export class ZipCompressNode {
339
339
  }
340
340
 
341
341
  /**
342
- * Compresses data using Zstandard (zstd) algorithm
343
- * @param input - Buffer to compress OR chunked reader object with totalSize and readChunk callback
342
+ * Compresses data using Zstandard via Node native zlib chunked streaming.
343
+ *
344
+ * @param input - Buffer OR chunked reader (`onReadChunk` preferred; `readChunk` accepted)
344
345
  * @param options - Compression options
345
- * @param bufferSize - Size of buffer to read if using chunked reader (default: 512KB)
346
- * @param entry - Optional ZIP entry for hash calculation
347
- * @param onOutputBuffer - Optional callback for streaming output
348
- * @returns Compressed data buffer
346
+ * @param bufferSize - Chunk size for streaming reads (default: 512KB)
347
+ * @param entry - Optional ZIP entry for hash / compressedSize
348
+ * @param onOutputBuffer - Optional callback for streaming compressed output
349
+ * @returns Compressed buffer, or empty buffer when streamed via onOutputBuffer
349
350
  */
350
351
  async zstdCompress(
351
- input: Buffer | { totalSize: number, readChunk: (position: number, size: number) => Buffer },
352
+ input:
353
+ | Buffer
354
+ | {
355
+ totalSize: number;
356
+ onReadChunk?: (position: number, size: number) => Buffer;
357
+ readChunk?: (position: number, size: number) => Buffer;
358
+ },
352
359
  options?: CompressOptions,
353
360
  bufferSize?: number,
354
361
  entry?: ZipEntry,
@@ -357,66 +364,112 @@ export class ZipCompressNode {
357
364
  const effectiveBufferSize = bufferSize || options?.bufferSize || this.zipkitNode.getBufferSize();
358
365
  const isBuffer = Buffer.isBuffer(input);
359
366
  const totalSize = isBuffer ? input.length : input.totalSize;
360
-
367
+
368
+ const needsHashCalculation =
369
+ entry && ((!entry.crc || entry.crc === 0) || (options?.useSHA256 && !entry.sha256));
370
+ const hashCalculator = needsHashCalculation
371
+ ? new HashCalculator({ useSHA256: !!(options?.useSHA256 && entry && !entry.sha256) })
372
+ : null;
373
+
361
374
  if (options?.level == 0) {
362
- // For store mode, return as-is
363
375
  if (isBuffer) {
376
+ if (hashCalculator && entry) {
377
+ hashCalculator.update(input);
378
+ if (!entry.crc || entry.crc === 0) {
379
+ entry.crc = hashCalculator.finalizeCRC32();
380
+ }
381
+ if (options?.useSHA256 && !entry.sha256) {
382
+ entry.sha256 = hashCalculator.finalizeSHA256();
383
+ }
384
+ }
364
385
  return input;
365
- } else {
366
- // Read all chunks
367
- const chunks: Buffer[] = [];
368
- let position = 0;
369
- while (position < totalSize) {
370
- const size = Math.min(effectiveBufferSize, totalSize - position);
371
- const chunk = input.readChunk(position, size);
386
+ }
387
+
388
+ const chunks: Buffer[] = [];
389
+ const read = input.onReadChunk ?? input.readChunk;
390
+ if (!read) {
391
+ throw new Error('ZSTD store mode: chunked reader missing read callback');
392
+ }
393
+ let position = 0;
394
+ while (position < totalSize) {
395
+ const size = Math.min(effectiveBufferSize, totalSize - position);
396
+ const chunk = read(position, size);
397
+ if (hashCalculator) {
398
+ hashCalculator.update(chunk);
399
+ }
400
+ if (onOutputBuffer) {
401
+ await onOutputBuffer(chunk);
402
+ } else {
372
403
  chunks.push(chunk);
373
- position += size;
374
404
  }
375
- return Buffer.concat(chunks);
405
+ position += chunk.length;
376
406
  }
407
+ if (hashCalculator && entry) {
408
+ if (!entry.crc || entry.crc === 0) {
409
+ entry.crc = hashCalculator.finalizeCRC32();
410
+ }
411
+ if (options?.useSHA256 && !entry.sha256) {
412
+ entry.sha256 = hashCalculator.finalizeSHA256();
413
+ }
414
+ }
415
+ return onOutputBuffer ? Buffer.alloc(0) : Buffer.concat(chunks);
377
416
  }
378
-
417
+
379
418
  try {
380
- // Zstd compression levels range from 1 (fastest) to 22 (highest compression)
381
- // Map our 1-9 level to a reasonable zstd range (1-19)
382
419
  const level = options?.level ?? 6;
383
- const zstdLevel = Math.min(Math.max(1, Math.floor(level * 2.1)), 19);
384
420
 
385
- // Get the full buffer (zstd doesn't support true streaming compression)
386
- const inbuf = isBuffer ? input : (() => {
387
- const chunks: Buffer[] = [];
388
- let position = 0;
389
- while (position < totalSize) {
390
- const size = Math.min(effectiveBufferSize, totalSize - position);
391
- const chunk = input.readChunk(position, size);
392
- chunks.push(chunk);
393
- position += size;
394
- }
395
- return Buffer.concat(chunks);
396
- })();
397
-
398
- // Validate input
399
- if (!inbuf || inbuf.length === 0) {
421
+ const read = isBuffer
422
+ ? (position: number, size: number) => input.subarray(position, position + size)
423
+ : (input.onReadChunk ?? input.readChunk);
424
+ if (!read) {
425
+ throw new Error('ZSTD compression: chunked reader missing read callback');
426
+ }
427
+ if (!totalSize) {
400
428
  throw new Error('ZSTD compression: empty input buffer');
401
429
  }
402
-
403
- // Convert Buffer to Uint8Array for WASM module
404
- const inputArray = new Uint8Array(inbuf.buffer, inbuf.byteOffset, inbuf.byteLength);
405
-
406
- // Compress the data with zstd using global ZstdManager
407
- const compressedData = await ZstdManager.compress(inputArray, zstdLevel);
408
- const compressedBuffer = Buffer.from(compressedData);
409
-
410
- // Set the compressed size in the entry for ZIP file structure
430
+
431
+ if (isBuffer && hashCalculator && entry) {
432
+ hashCalculator.update(input);
433
+ entry.crc = hashCalculator.finalizeCRC32();
434
+ if (options?.useSHA256 && !entry.sha256) {
435
+ entry.sha256 = hashCalculator.finalizeSHA256();
436
+ }
437
+ }
438
+
439
+ const collected: Buffer[] = [];
440
+ const compressedSize = await ZstdNode.compressChunks(
441
+ read,
442
+ totalSize,
443
+ effectiveBufferSize,
444
+ level,
445
+ isBuffer
446
+ ? undefined
447
+ : (plain) => {
448
+ hashCalculator?.update(plain);
449
+ },
450
+ async (compressed) => {
451
+ if (onOutputBuffer) {
452
+ await onOutputBuffer(compressed);
453
+ } else {
454
+ collected.push(compressed);
455
+ }
456
+ }
457
+ );
458
+
459
+ if (!isBuffer && hashCalculator && entry) {
460
+ entry.crc = hashCalculator.finalizeCRC32();
461
+ if (options?.useSHA256 && !entry.sha256) {
462
+ entry.sha256 = hashCalculator.finalizeSHA256();
463
+ }
464
+ }
411
465
  if (entry) {
412
- entry.compressedSize = compressedBuffer.length;
466
+ entry.compressedSize = compressedSize;
413
467
  }
414
-
468
+
415
469
  if (onOutputBuffer) {
416
- await onOutputBuffer(compressedBuffer);
470
+ return Buffer.alloc(0);
417
471
  }
418
-
419
- return compressedBuffer;
472
+ return Buffer.concat(collected);
420
473
  } catch (e) {
421
474
  Logger.error('Error during zstd compression:', e);
422
475
  throw new Error(Errors.COMPRESSION_ERROR);
@@ -591,9 +644,8 @@ export class ZipCompressNode {
591
644
  const fileData = fs.readFileSync(filePath);
592
645
  return await this.compressData(entry, fileData, options, onOutputBuffer);
593
646
  } else if (compressionMethod === 'ZSTD') {
594
- // ZSTD requires full buffer, so read file first
595
- const fileData = fs.readFileSync(filePath);
596
- return await this.compressData(entry, fileData, options, onOutputBuffer);
647
+ // Native Node zstd chunked streaming
648
+ return await this.zstdCompress(chunkedReader, options, bufferSize, entry, onOutputBuffer);
597
649
  } else {
598
650
  // DEFLATED: Use deflateCompress with chunked reader
599
651
  return await this.deflateCompress(chunkedReader, options, bufferSize, entry, onOutputBuffer);