@push.rocks/smartarchive 4.2.4 → 5.0.0

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 (37) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/bzip2/bititerator.d.ts +6 -1
  3. package/dist_ts/bzip2/bititerator.js +30 -24
  4. package/dist_ts/bzip2/bzip2.d.ts +27 -12
  5. package/dist_ts/bzip2/bzip2.js +325 -263
  6. package/dist_ts/bzip2/index.d.ts +4 -1
  7. package/dist_ts/bzip2/index.js +36 -38
  8. package/dist_ts/classes.archiveanalyzer.d.ts +25 -5
  9. package/dist_ts/classes.archiveanalyzer.js +19 -8
  10. package/dist_ts/classes.bzip2tools.d.ts +1 -1
  11. package/dist_ts/classes.gziptools.d.ts +39 -6
  12. package/dist_ts/classes.gziptools.js +85 -24
  13. package/dist_ts/classes.smartarchive.d.ts +101 -16
  14. package/dist_ts/classes.smartarchive.js +395 -58
  15. package/dist_ts/classes.tartools.d.ts +31 -4
  16. package/dist_ts/classes.tartools.js +93 -32
  17. package/dist_ts/classes.ziptools.d.ts +47 -12
  18. package/dist_ts/classes.ziptools.js +142 -23
  19. package/dist_ts/errors.d.ts +44 -0
  20. package/dist_ts/errors.js +62 -0
  21. package/dist_ts/index.d.ts +4 -0
  22. package/dist_ts/index.js +9 -1
  23. package/dist_ts/interfaces.d.ts +115 -0
  24. package/dist_ts/interfaces.js +2 -0
  25. package/package.json +1 -1
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/bzip2/bititerator.ts +43 -27
  28. package/ts/bzip2/bzip2.ts +289 -171
  29. package/ts/bzip2/index.ts +52 -50
  30. package/ts/classes.archiveanalyzer.ts +42 -28
  31. package/ts/classes.gziptools.ts +96 -33
  32. package/ts/classes.smartarchive.ts +469 -118
  33. package/ts/classes.tartools.ts +124 -52
  34. package/ts/classes.ziptools.ts +160 -34
  35. package/ts/errors.ts +70 -0
  36. package/ts/index.ts +11 -0
  37. package/ts/interfaces.ts +131 -0
package/ts/bzip2/index.ts CHANGED
@@ -1,51 +1,53 @@
1
1
  import * as plugins from '../plugins.js';
2
+ import { Bzip2Error, BZIP2_ERROR_CODES } from '../errors.js';
3
+ import type { IBitReader } from '../interfaces.js';
2
4
 
3
5
  import { Bzip2 } from './bzip2.js';
4
6
  import { bitIterator } from './bititerator.js';
5
7
 
6
- export function unbzip2Stream() {
8
+ /**
9
+ * Creates a streaming BZIP2 decompression transform
10
+ */
11
+ export function unbzip2Stream(): plugins.smartstream.SmartDuplex<Buffer, Buffer> {
7
12
  const bzip2Instance = new Bzip2();
8
- var bufferQueue = [];
9
- var hasBytes = 0;
10
- var blockSize = 0;
11
- var broken = false;
12
- var done = false;
13
- var bitReader = null;
14
- var streamCRC = null;
13
+ const bufferQueue: Buffer[] = [];
14
+ let hasBytes = 0;
15
+ let blockSize = 0;
16
+ let broken = false;
17
+ let bitReader: IBitReader | null = null;
18
+ let streamCRC: number | null = null;
15
19
 
16
- function decompressBlock() {
20
+ function decompressBlock(): Buffer | undefined {
17
21
  if (!blockSize) {
18
- blockSize = bzip2Instance.header(bitReader);
22
+ blockSize = bzip2Instance.header(bitReader!);
19
23
  streamCRC = 0;
20
- } else {
21
- var bufsize = 100000 * blockSize;
22
- var buf = new Int32Array(bufsize);
24
+ return undefined;
25
+ }
23
26
 
24
- var chunk = [];
25
- var f = function (b) {
26
- chunk.push(b);
27
- };
27
+ const bufsize = 100000 * blockSize;
28
+ const buf = new Int32Array(bufsize);
29
+ const chunk: number[] = [];
28
30
 
29
- streamCRC = bzip2Instance.decompress(
30
- bitReader,
31
- f,
32
- buf,
33
- bufsize,
34
- streamCRC,
35
- );
36
- if (streamCRC === null) {
37
- // reset for next bzip2 header
38
- blockSize = 0;
39
- return;
40
- } else {
41
- return Buffer.from(chunk);
42
- }
31
+ const outputFunc = (b: number): void => {
32
+ chunk.push(b);
33
+ };
34
+
35
+ streamCRC = bzip2Instance.decompress(bitReader!, outputFunc, buf, bufsize, streamCRC);
36
+
37
+ if (streamCRC === null) {
38
+ // Reset for next bzip2 header
39
+ blockSize = 0;
40
+ return undefined;
43
41
  }
42
+
43
+ return Buffer.from(chunk);
44
44
  }
45
45
 
46
- var outlength = 0;
47
- const decompressAndPush = async () => {
48
- if (broken) return;
46
+ let outlength = 0;
47
+
48
+ const decompressAndPush = async (): Promise<Buffer | undefined> => {
49
+ if (broken) return undefined;
50
+
49
51
  try {
50
52
  const resultChunk = decompressBlock();
51
53
  if (resultChunk) {
@@ -53,40 +55,39 @@ export function unbzip2Stream() {
53
55
  }
54
56
  return resultChunk;
55
57
  } catch (e) {
56
- console.error(e);
57
58
  broken = true;
59
+ if (e instanceof Error) {
60
+ throw new Bzip2Error(`Decompression failed: ${e.message}`, BZIP2_ERROR_CODES.INVALID_BLOCK_DATA);
61
+ }
62
+ throw e;
58
63
  }
59
64
  };
60
- let counter = 0;
61
- return new plugins.smartstream.SmartDuplex({
65
+
66
+ return new plugins.smartstream.SmartDuplex<Buffer, Buffer>({
62
67
  objectMode: true,
63
68
  name: 'bzip2',
64
- debug: false,
65
69
  highWaterMark: 1,
66
70
  writeFunction: async function (data, streamTools) {
67
- // console.log(`got chunk ${counter++}`)
68
71
  bufferQueue.push(data);
69
72
  hasBytes += data.length;
73
+
70
74
  if (bitReader === null) {
71
75
  bitReader = bitIterator(function () {
72
- return bufferQueue.shift();
76
+ return bufferQueue.shift()!;
73
77
  });
74
78
  }
75
- while (
76
- !broken &&
77
- hasBytes - bitReader.bytesRead + 1 >= (25000 + 100000 * blockSize || 4)
78
- ) {
79
- //console.error('decompressing with', hasBytes - bitReader.bytesRead + 1, 'bytes in buffer');
79
+
80
+ const threshold = 25000 + 100000 * blockSize || 4;
81
+ while (!broken && hasBytes - bitReader.bytesRead + 1 >= threshold) {
80
82
  const result = await decompressAndPush();
81
83
  if (!result) {
82
84
  continue;
83
85
  }
84
- // console.log(result.toString());
85
86
  await streamTools.push(result);
86
87
  }
88
+ return null;
87
89
  },
88
90
  finalFunction: async function (streamTools) {
89
- //console.error(x,'last compressing with', hasBytes, 'bytes in buffer');
90
91
  while (!broken && bitReader && hasBytes > bitReader.bytesRead) {
91
92
  const result = await decompressAndPush();
92
93
  if (!result) {
@@ -94,10 +95,11 @@ export function unbzip2Stream() {
94
95
  }
95
96
  await streamTools.push(result);
96
97
  }
97
- if (!broken) {
98
- if (streamCRC !== null)
99
- this.emit('error', new Error('input stream ended prematurely'));
98
+
99
+ if (!broken && streamCRC !== null) {
100
+ this.emit('error', new Bzip2Error('Input stream ended prematurely', BZIP2_ERROR_CODES.PREMATURE_END));
100
101
  }
102
+ return null;
101
103
  },
102
104
  });
103
105
  }
@@ -1,24 +1,41 @@
1
1
  import type { SmartArchive } from './classes.smartarchive.js';
2
+ import type { TSupportedMime } from './interfaces.js';
2
3
  import * as plugins from './plugins.js';
3
4
 
5
+ /**
6
+ * Type for decompression streams
7
+ */
8
+ export type TDecompressionStream =
9
+ | plugins.stream.Transform
10
+ | plugins.stream.Duplex
11
+ | plugins.tarStream.Extract;
12
+
13
+ /**
14
+ * Result of archive analysis
15
+ */
4
16
  export interface IAnalyzedResult {
5
- fileType: plugins.fileType.FileTypeResult;
17
+ fileType: plugins.fileType.FileTypeResult | undefined;
6
18
  isArchive: boolean;
7
- resultStream: plugins.smartstream.SmartDuplex;
8
- decompressionStream:
9
- | plugins.stream.Transform
10
- | plugins.stream.Duplex
11
- | plugins.tarStream.Extract;
19
+ resultStream: plugins.smartstream.SmartDuplex<Buffer, Buffer>;
20
+ decompressionStream: TDecompressionStream;
12
21
  }
13
22
 
23
+ /**
24
+ * Analyzes archive streams to detect format and provide decompression
25
+ */
14
26
  export class ArchiveAnalyzer {
15
- smartArchiveRef: SmartArchive;
27
+ private smartArchiveRef: SmartArchive;
16
28
 
17
29
  constructor(smartArchiveRefArg: SmartArchive) {
18
30
  this.smartArchiveRef = smartArchiveRefArg;
19
31
  }
20
32
 
21
- private async mimeTypeIsArchive(mimeType: string): Promise<boolean> {
33
+ /**
34
+ * Check if a MIME type represents an archive format
35
+ */
36
+ private async mimeTypeIsArchive(mimeType: string | undefined): Promise<boolean> {
37
+ if (!mimeType) return false;
38
+
22
39
  const archiveMimeTypes: Set<string> = new Set([
23
40
  'application/zip',
24
41
  'application/x-rar-compressed',
@@ -26,50 +43,46 @@ export class ArchiveAnalyzer {
26
43
  'application/gzip',
27
44
  'application/x-7z-compressed',
28
45
  'application/x-bzip2',
29
- // Add other archive mime types here
30
46
  ]);
31
47
 
32
48
  return archiveMimeTypes.has(mimeType);
33
49
  }
34
50
 
35
- private async getDecompressionStream(
36
- mimeTypeArg: plugins.fileType.FileTypeResult['mime'],
37
- ): Promise<
38
- plugins.stream.Transform | plugins.stream.Duplex | plugins.tarStream.Extract
39
- > {
51
+ /**
52
+ * Get the appropriate decompression stream for a MIME type
53
+ */
54
+ private async getDecompressionStream(mimeTypeArg: TSupportedMime): Promise<TDecompressionStream> {
40
55
  switch (mimeTypeArg) {
41
56
  case 'application/gzip':
42
57
  return this.smartArchiveRef.gzipTools.getDecompressionStream();
43
58
  case 'application/zip':
44
59
  return this.smartArchiveRef.zipTools.getDecompressionStream();
45
60
  case 'application/x-bzip2':
46
- return await this.smartArchiveRef.bzip2Tools.getDecompressionStream(); // replace with your own bzip2 decompression stream
61
+ return this.smartArchiveRef.bzip2Tools.getDecompressionStream();
47
62
  case 'application/x-tar':
48
- return this.smartArchiveRef.tarTools.getDecompressionStream(); // replace with your own tar decompression stream
63
+ return this.smartArchiveRef.tarTools.getDecompressionStream();
49
64
  default:
50
65
  // Handle unsupported formats or no decompression needed
51
66
  return plugins.smartstream.createPassThrough();
52
67
  }
53
68
  }
54
69
 
55
- public getAnalyzedStream() {
70
+ /**
71
+ * Create an analyzed stream that detects archive type and provides decompression
72
+ * Emits a single IAnalyzedResult object
73
+ */
74
+ public getAnalyzedStream(): plugins.smartstream.SmartDuplex<Buffer, IAnalyzedResult> {
56
75
  let firstRun = true;
57
76
  const resultStream = plugins.smartstream.createPassThrough();
58
- const analyzerstream = new plugins.smartstream.SmartDuplex<
59
- Buffer,
60
- IAnalyzedResult
61
- >({
77
+
78
+ const analyzerstream = new plugins.smartstream.SmartDuplex<Buffer, IAnalyzedResult>({
62
79
  readableObjectMode: true,
63
80
  writeFunction: async (chunkArg: Buffer, streamtools) => {
64
81
  if (firstRun) {
65
82
  firstRun = false;
66
83
  const fileType = await plugins.fileType.fileTypeFromBuffer(chunkArg);
67
- const decompressionStream = await this.getDecompressionStream(
68
- fileType?.mime as any,
69
- );
70
- /**
71
- * analyzed stream emits once with this object
72
- */
84
+ const decompressionStream = await this.getDecompressionStream(fileType?.mime as TSupportedMime);
85
+
73
86
  const result: IAnalyzedResult = {
74
87
  fileType,
75
88
  isArchive: await this.mimeTypeIsArchive(fileType?.mime),
@@ -81,11 +94,12 @@ export class ArchiveAnalyzer {
81
94
  await resultStream.backpressuredPush(chunkArg);
82
95
  return null;
83
96
  },
84
- finalFunction: async (tools) => {
97
+ finalFunction: async () => {
85
98
  resultStream.push(null);
86
99
  return null;
87
100
  },
88
101
  });
102
+
89
103
  return analyzerstream;
90
104
  }
91
105
  }
@@ -1,42 +1,60 @@
1
- import type { SmartArchive } from './classes.smartarchive.js';
2
1
  import * as plugins from './plugins.js';
2
+ import type { TCompressionLevel } from './interfaces.js';
3
3
 
4
- // This class wraps fflate's gunzip in a Node.js Transform stream
5
- export class CompressGunzipTransform extends plugins.stream.Transform {
6
- constructor() {
4
+ /**
5
+ * Transform stream for GZIP compression using fflate
6
+ */
7
+ export class GzipCompressionTransform extends plugins.stream.Transform {
8
+ private gzip: plugins.fflate.Gzip;
9
+
10
+ constructor(level: TCompressionLevel = 6) {
7
11
  super();
12
+
13
+ // Create a streaming Gzip compressor
14
+ this.gzip = new plugins.fflate.Gzip({ level }, (chunk, final) => {
15
+ this.push(Buffer.from(chunk));
16
+ if (final) {
17
+ this.push(null);
18
+ }
19
+ });
8
20
  }
9
21
 
10
22
  _transform(
11
23
  chunk: Buffer,
12
24
  encoding: BufferEncoding,
13
- callback: plugins.stream.TransformCallback,
14
- ) {
15
- plugins.fflate.gunzip(chunk, (err, decompressed) => {
16
- if (err) {
17
- callback(err);
18
- } else {
19
- this.push(decompressed);
20
- callback();
21
- }
22
- });
25
+ callback: plugins.stream.TransformCallback
26
+ ): void {
27
+ try {
28
+ this.gzip.push(chunk, false);
29
+ callback();
30
+ } catch (err) {
31
+ callback(err as Error);
32
+ }
33
+ }
34
+
35
+ _flush(callback: plugins.stream.TransformCallback): void {
36
+ try {
37
+ this.gzip.push(new Uint8Array(0), true);
38
+ callback();
39
+ } catch (err) {
40
+ callback(err as Error);
41
+ }
23
42
  }
24
43
  }
25
44
 
26
- // DecompressGunzipTransform class that extends the Node.js Transform stream to
27
- // create a stream that decompresses GZip-compressed data using fflate's gunzip function
28
- export class DecompressGunzipTransform extends plugins.stream.Transform {
29
- private gunzip: any; // fflate.Gunzip instance
30
-
45
+ /**
46
+ * Transform stream for GZIP decompression using fflate
47
+ */
48
+ export class GzipDecompressionTransform extends plugins.stream.Transform {
49
+ private gunzip: plugins.fflate.Gunzip;
50
+
31
51
  constructor() {
32
52
  super();
33
-
53
+
34
54
  // Create a streaming Gunzip decompressor
35
55
  this.gunzip = new plugins.fflate.Gunzip((chunk, final) => {
36
- // Push decompressed chunks to the output stream
37
56
  this.push(Buffer.from(chunk));
38
57
  if (final) {
39
- // Signal end of stream when decompression is complete
40
58
  this.push(null);
41
59
  }
42
60
  });
@@ -45,20 +63,18 @@ export class DecompressGunzipTransform extends plugins.stream.Transform {
45
63
  _transform(
46
64
  chunk: Buffer,
47
65
  encoding: BufferEncoding,
48
- callback: plugins.stream.TransformCallback,
49
- ) {
66
+ callback: plugins.stream.TransformCallback
67
+ ): void {
50
68
  try {
51
- // Feed chunks to the gunzip stream
52
69
  this.gunzip.push(chunk, false);
53
70
  callback();
54
71
  } catch (err) {
55
72
  callback(err as Error);
56
73
  }
57
74
  }
58
-
59
- _flush(callback: plugins.stream.TransformCallback) {
75
+
76
+ _flush(callback: plugins.stream.TransformCallback): void {
60
77
  try {
61
- // Signal end of input to gunzip
62
78
  this.gunzip.push(new Uint8Array(0), true);
63
79
  callback();
64
80
  } catch (err) {
@@ -67,14 +83,61 @@ export class DecompressGunzipTransform extends plugins.stream.Transform {
67
83
  }
68
84
  }
69
85
 
86
+ /**
87
+ * GZIP compression and decompression utilities
88
+ */
70
89
  export class GzipTools {
71
- constructor() {}
90
+ /**
91
+ * Get a streaming compression transform
92
+ */
93
+ public getCompressionStream(level?: TCompressionLevel): plugins.stream.Transform {
94
+ return new GzipCompressionTransform(level);
95
+ }
72
96
 
73
- public getCompressionStream() {
74
- return new CompressGunzipTransform();
97
+ /**
98
+ * Get a streaming decompression transform
99
+ */
100
+ public getDecompressionStream(): plugins.stream.Transform {
101
+ return new GzipDecompressionTransform();
75
102
  }
76
103
 
77
- public getDecompressionStream() {
78
- return new DecompressGunzipTransform();
104
+ /**
105
+ * Compress data synchronously
106
+ */
107
+ public compressSync(data: Buffer, level?: TCompressionLevel): Buffer {
108
+ const options = level !== undefined ? { level } : undefined;
109
+ return Buffer.from(plugins.fflate.gzipSync(data, options));
110
+ }
111
+
112
+ /**
113
+ * Decompress data synchronously
114
+ */
115
+ public decompressSync(data: Buffer): Buffer {
116
+ return Buffer.from(plugins.fflate.gunzipSync(data));
117
+ }
118
+
119
+ /**
120
+ * Compress data asynchronously
121
+ */
122
+ public async compress(data: Buffer, level?: TCompressionLevel): Promise<Buffer> {
123
+ return new Promise((resolve, reject) => {
124
+ const options = level !== undefined ? { level } : undefined;
125
+ plugins.fflate.gzip(data, options as plugins.fflate.AsyncGzipOptions, (err, result) => {
126
+ if (err) reject(err);
127
+ else resolve(Buffer.from(result));
128
+ });
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Decompress data asynchronously
134
+ */
135
+ public async decompress(data: Buffer): Promise<Buffer> {
136
+ return new Promise((resolve, reject) => {
137
+ plugins.fflate.gunzip(data, (err, result) => {
138
+ if (err) reject(err);
139
+ else resolve(Buffer.from(result));
140
+ });
141
+ });
79
142
  }
80
143
  }