@hpcc-js/wasm-zstd 1.7.0 → 1.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hpcc-js/wasm-zstd",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "hpcc-js - WASM zstd",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,6 +18,8 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "clean": "rimraf coverage dist dist-test types .nyc_output",
21
+ "build-cpp": "cmake --build ../../build --target zstdlib",
22
+ "build-cpp-watch": "chokidar 'src-cpp/**.*' -c 'npm run build-cpp'",
21
23
  "gen-types": "tsc --project tsconfig.json --emitDeclarationOnly",
22
24
  "gen-types-watch": "npm run gen-types -- --watch",
23
25
  "bundle": "node esbuild.js",
@@ -26,7 +28,7 @@
26
28
  "build-dev": "run-p gen-types bundle-dev",
27
29
  "build": "run-p gen-types bundle",
28
30
  "lint-skypack": "npx -y @skypack/package-check",
29
- "lint-eslint": "eslint src/**/*.ts tests/**/*.ts",
31
+ "lint-eslint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
30
32
  "lint": "run-p lint-eslint",
31
33
  "test-browser": "vitest run --project browser",
32
34
  "test-node": "vitest run --project node",
@@ -36,7 +38,8 @@
36
38
  "update-major": "npx -y npm-check-updates -u"
37
39
  },
38
40
  "devDependencies": {
39
- "@hpcc-js/esbuild-plugins": "1.5.2"
41
+ "@hpcc-js/esbuild-plugins": "1.7.0",
42
+ "@hpcc-js/wasm-util": "1.0.0"
40
43
  },
41
44
  "keywords": [
42
45
  "graphviz",
@@ -54,5 +57,5 @@
54
57
  },
55
58
  "homepage": "https://hpcc-systems.github.io/hpcc-js-wasm/",
56
59
  "license": "Apache-2.0",
57
- "gitHead": "bd1b2a90ecccc79105da683bd2cfbc69296b693e"
60
+ "gitHead": "6799e024fc9186c4a4f87294862142f00628e597"
58
61
  }
package/src/zstd.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // @ts-expect-error importing from a wasm file is resolved via a custom esbuild plugin
2
- import load, { reset } from "../../../build/packages/zstd/src-cpp/zstdlib.wasm";
3
- import { WasmLibrary } from "./wasm-library.ts";
2
+ import load, { reset } from "../../../build/packages/zstd/zstdlib.wasm";
3
+ import type { MainModule, zstd } from "../../../build/packages/zstd/zstdlib.js";
4
+ import { MainModuleEx } from "@hpcc-js/wasm-util";
5
+ type ZstdExports = MainModule["zstd"];
4
6
 
5
7
  // Ref: http://facebook.github.io/zstd/zstd_manual.html
6
8
  // Ref: https://github.com/facebook/zstd
@@ -24,10 +26,14 @@ let g_zstd: Promise<Zstd>;
24
26
  * const decompressed_data = zstd.decompress(compressed_data);
25
27
  * ```
26
28
  */
27
- export class Zstd extends WasmLibrary {
28
-
29
- private constructor(_module: any) {
30
- super(_module, _module.zstd.prototype);
29
+ export class Zstd extends MainModuleEx<MainModule> {
30
+ private _zstdClass: ZstdExports;
31
+ private _zstd: zstd;
32
+
33
+ private constructor(_module: MainModule) {
34
+ super(_module);
35
+ this._zstdClass = _module.zstd;
36
+ this._zstd = new this._zstdClass();
31
37
  }
32
38
 
33
39
  /**
@@ -41,9 +47,7 @@ export class Zstd extends WasmLibrary {
41
47
  */
42
48
  static load(): Promise<Zstd> {
43
49
  if (!g_zstd) {
44
- g_zstd = load().then((module: any) => {
45
- return new Zstd(module)
46
- });
50
+ g_zstd = (load() as Promise<MainModule>).then((module) => new Zstd(module));
47
51
  }
48
52
  return g_zstd;
49
53
  }
@@ -59,7 +63,22 @@ export class Zstd extends WasmLibrary {
59
63
  * @returns The Zstd c++ version
60
64
  */
61
65
  version(): string {
62
- return this._exports.version();
66
+ return this._zstdClass.version();
67
+ }
68
+
69
+ /**
70
+ * Resets the internal compression/decompression state.
71
+ */
72
+ reset(): void {
73
+ this._zstd.reset();
74
+ }
75
+
76
+ /**
77
+ * Sets the compression level for streaming compression.
78
+ * @param level Compression level (use minCLevel() to maxCLevel())
79
+ */
80
+ setCompressionLevel(level: number): void {
81
+ this._zstd.setCompressionLevel(level);
63
82
  }
64
83
 
65
84
  /**
@@ -72,19 +91,76 @@ export class Zstd extends WasmLibrary {
72
91
  * :::
73
92
  */
74
93
  compress(data: Uint8Array, compressionLevel: number = this.defaultCLevel()): Uint8Array {
75
- const uncompressed = this.uint8_heapu8(data);
94
+ const uncompressed = this.dataToHeap(data);
76
95
 
77
- const compressedSize = this._exports.compressBound(data.length);
78
- const compressed = this.malloc_heapu8(compressedSize);
79
- compressed.size = this._exports.compress(compressed.ptr, compressedSize, uncompressed.ptr, uncompressed.size, compressionLevel);
96
+ const compressedSize = this._zstdClass.compressBound(data.length);
97
+ const compressed = this.malloc(compressedSize);
98
+ compressed.size = this._zstdClass.compress(compressed.ptr, compressedSize, uncompressed.ptr, uncompressed.size, compressionLevel);
80
99
  /* istanbul ignore if */
81
- if (this._exports.isError(compressed.size)) {
82
- console.error(this._exports.getErrorName(compressed.size));
100
+ if (this._zstdClass.isError(compressed.size)) {
101
+ console.error(this._zstdClass.getErrorName(compressed.size));
102
+ }
103
+ const retVal = this.heapToUint8Array(compressed);
104
+
105
+ this.free(compressed);
106
+ this.free(uncompressed);
107
+ return retVal;
108
+ }
109
+
110
+ /**
111
+ * Compresses a chunk of data in streaming mode.
112
+ * Call reset() before the first chunk, then compressChunk() for each chunk, and finally compressEnd().
113
+ * @param data Chunk of data to be compressed
114
+ * @returns Compressed chunk data
115
+ */
116
+ compressChunk(data: Uint8Array): Uint8Array {
117
+ const uncompressed = this.dataToHeap(data);
118
+ // For streaming compression, we need enough space for:
119
+ // 1. The compressed data (compressBound gives worst case)
120
+ // 2. Additional overhead for frame headers and internal buffering
121
+ // Use compressBound + CStreamOutSize to ensure we have enough
122
+ const boundSize = this._zstdClass.compressBound(data.length);
123
+ const streamOutSize = this._zstdClass.CStreamOutSize();
124
+ const compressedSize = boundSize + streamOutSize;
125
+ const compressed = this.malloc(compressedSize);
126
+
127
+ compressed.size = this._zstd.compressChunk(compressed.ptr, compressedSize, uncompressed.ptr, uncompressed.size);
128
+
129
+ // Check for errors before trying to use the size
130
+ if (this._zstdClass.isError(compressed.size)) {
131
+ const errorName = this._zstdClass.getErrorName(compressed.size);
132
+ this.free(compressed);
133
+ this.free(uncompressed);
134
+ throw new Error(`compressChunk failed: ${errorName} (data.length=${data.length}, compressedSize=${compressedSize})`);
83
135
  }
84
- const retVal = this.heapu8_uint8(compressed);
85
136
 
86
- this.free_heapu8(compressed);
87
- this.free_heapu8(uncompressed);
137
+ const retVal = this.heapToUint8Array(compressed);
138
+
139
+ this.free(compressed);
140
+ this.free(uncompressed);
141
+ return retVal;
142
+ }
143
+
144
+ /**
145
+ * Finishes the streaming compression and returns any remaining compressed data.
146
+ * @returns Final compressed data
147
+ */
148
+ compressEnd(): Uint8Array {
149
+ const compressedSize = this._zstdClass.CStreamOutSize(); // Recommended buffer size for output
150
+ const compressed = this.malloc(compressedSize);
151
+
152
+ compressed.size = this._zstd.compressEnd(compressed.ptr, compressedSize);
153
+
154
+ // Check for errors before trying to use the size
155
+ if (this._zstdClass.isError(compressed.size)) {
156
+ const errorName = this._zstdClass.getErrorName(compressed.size);
157
+ this.free(compressed);
158
+ throw new Error(`compressEnd failed: ${errorName} (compressedSize=${compressedSize})`);
159
+ }
160
+
161
+ const retVal = this.heapToUint8Array(compressed);
162
+
163
+ this.free(compressed);
88
164
  return retVal;
89
165
  }
90
166
 
@@ -93,23 +169,62 @@ export class Zstd extends WasmLibrary {
93
169
  * @returns Uncompressed data.
94
170
  */
95
171
  decompress(compressedData: Uint8Array): Uint8Array {
96
- const compressed = this.uint8_heapu8(compressedData);
97
- const uncompressedSize = this._exports.getFrameContentSize(compressed.ptr, compressed.size);
172
+ const compressed = this.dataToHeap(compressedData);
173
+ let uncompressedSize = this._zstdClass.getFrameContentSize(compressed.ptr, compressed.size);
174
+
175
+ // Check if size is unknown (happens with streaming compression)
176
+ // ZSTD_CONTENTSIZE_UNKNOWN is (uint64_t)-1, which becomes a very large number in JS
177
+ // Using BigInt to avoid precision loss warning
178
+ const CONTENTSIZE_UNKNOWN = BigInt("0xFFFFFFFFFFFFFFFF");
179
+
98
180
  /* istanbul ignore if */
99
- if (this._exports.isError(uncompressedSize)) {
100
- console.error(this._exports.getErrorName(uncompressedSize));
181
+ if (this._zstdClass.isError(uncompressedSize)) {
182
+ const errorName = this._zstdClass.getErrorName(uncompressedSize);
183
+ this.free(compressed);
184
+ throw new Error(`Failed to get frame content size: ${errorName}`);
101
185
  }
102
- const uncompressed = this.malloc_heapu8(uncompressedSize);
103
186
 
104
- uncompressed.size = this._exports.decompress(uncompressed.ptr, uncompressedSize, compressed.ptr, compressed.size);
187
+ // If content size is unknown, use a reasonable upper bound
188
+ // For safety, use decompression bound or a multiple of compressed size
189
+ if (BigInt(uncompressedSize) >= CONTENTSIZE_UNKNOWN || uncompressedSize === 0) {
190
+ // Use a heuristic: decompressed data is typically 2-10x compressed size for text/structured data
191
+ // Allocate generously to avoid buffer overflow
192
+ uncompressedSize = Math.max(compressed.size * 20, 1024 * 1024); // At least 1MB or 20x compressed
193
+ }
194
+
195
+ const uncompressed = this.malloc(uncompressedSize);
196
+
197
+ uncompressed.size = this._zstdClass.decompress(uncompressed.ptr, uncompressedSize, compressed.ptr, compressed.size);
105
198
  /* istanbul ignore if */
106
- if (this._exports.isError(uncompressed.size)) {
107
- console.error(this._exports.getErrorName(uncompressed.size));
199
+ if (this._zstdClass.isError(uncompressed.size)) {
200
+ const errorName = this._zstdClass.getErrorName(uncompressed.size);
201
+ this.free(uncompressed);
202
+ this.free(compressed);
203
+ throw new Error(`Decompression failed: ${errorName}`);
108
204
  }
109
- const retVal = this.heapu8_uint8(uncompressed);
205
+ const retVal = this.heapToUint8Array(uncompressed);
206
+
207
+ this.free(uncompressed);
208
+ this.free(compressed);
209
+ return retVal;
210
+ }
211
+
212
+ /**
213
+ * Decompresses a chunk of data in streaming mode.
214
+ * Call reset() before the first chunk, then decompressChunk() for each chunk.
215
+ * @param compressedData Chunk of compressed data
216
+ * @param outputSize Expected output size for this chunk
217
+ * @returns Decompressed chunk data
218
+ */
219
+ decompressChunk(compressedData: Uint8Array, outputSize: number): Uint8Array {
220
+ const compressed = this.dataToHeap(compressedData);
221
+ const uncompressed = this.malloc(outputSize);
222
+
223
+ uncompressed.size = this._zstd.decompressChunk(uncompressed.ptr, outputSize, compressed.ptr, compressed.size);
224
+ const retVal = this.heapToUint8Array(uncompressed);
110
225
 
111
- this.free_heapu8(uncompressed);
112
- this.free_heapu8(compressed);
226
+ this.free(uncompressed);
227
+ this.free(compressed);
113
228
  return retVal;
114
229
  }
115
230
 
@@ -117,14 +232,14 @@ export class Zstd extends WasmLibrary {
117
232
  * @returns Default compression level (see notes above above).
118
233
  */
119
234
  defaultCLevel(): number {
120
- return this._exports.defaultCLevel();
235
+ return this._zstdClass.defaultCLevel();
121
236
  }
122
237
 
123
238
  minCLevel(): number {
124
- return this._exports.minCLevel();
239
+ return this._zstdClass.minCLevel();
125
240
  }
126
241
 
127
242
  maxCLevel(): number {
128
- return this._exports.maxCLevel();
243
+ return this._zstdClass.maxCLevel();
129
244
  }
130
245
  }
@@ -1,3 +1,3 @@
1
1
  export declare const PKG_NAME = "@hpcc-js/wasm-zstd";
2
- export declare const PKG_VERSION = "1.5.1";
3
- export declare const BUILD_VERSION = "3.12.0";
2
+ export declare const PKG_VERSION = "1.7.0";
3
+ export declare const BUILD_VERSION = "4.0.0";
package/types/zstd.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { WasmLibrary } from "./wasm-library.ts";
1
+ import type { MainModule } from "../../../build/packages/zstd/zstdlib.js";
2
+ import { MainModuleEx } from "@hpcc-js/wasm-util";
2
3
  /**
3
4
  * The Zstandard WASM library, provides a simplified wrapper around the Zstandard c++ library.
4
5
  *
@@ -16,7 +17,9 @@ import { WasmLibrary } from "./wasm-library.ts";
16
17
  * const decompressed_data = zstd.decompress(compressed_data);
17
18
  * ```
18
19
  */
19
- export declare class Zstd extends WasmLibrary {
20
+ export declare class Zstd extends MainModuleEx<MainModule> {
21
+ private _zstdClass;
22
+ private _zstd;
20
23
  private constructor();
21
24
  /**
22
25
  * Compiles and instantiates the raw wasm.
@@ -36,6 +39,15 @@ export declare class Zstd extends WasmLibrary {
36
39
  * @returns The Zstd c++ version
37
40
  */
38
41
  version(): string;
42
+ /**
43
+ * Resets the internal compression/decompression state.
44
+ */
45
+ reset(): void;
46
+ /**
47
+ * Sets the compression level for streaming compression.
48
+ * @param level Compression level (use minCLevel() to maxCLevel())
49
+ */
50
+ setCompressionLevel(level: number): void;
39
51
  /**
40
52
  * @param data Data to be compressed
41
53
  * @param compressionLevel Compression v Speed tradeoff, when omitted it will default to `zstd.defaultCLevel()` which is currently 3.
@@ -46,11 +58,31 @@ export declare class Zstd extends WasmLibrary {
46
58
  * :::
47
59
  */
48
60
  compress(data: Uint8Array, compressionLevel?: number): Uint8Array;
61
+ /**
62
+ * Compresses a chunk of data in streaming mode.
63
+ * Call reset() before the first chunk, then compressChunk() for each chunk, and finally compressEnd().
64
+ * @param data Chunk of data to be compressed
65
+ * @returns Compressed chunk data
66
+ */
67
+ compressChunk(data: Uint8Array): Uint8Array;
68
+ /**
69
+ * Finishes the streaming compression and returns any remaining compressed data.
70
+ * @returns Final compressed data
71
+ */
72
+ compressEnd(): Uint8Array;
49
73
  /**
50
74
  * @param compressedData Data to be compressed
51
75
  * @returns Uncompressed data.
52
76
  */
53
77
  decompress(compressedData: Uint8Array): Uint8Array;
78
+ /**
79
+ * Decompresses a chunk of data in streaming mode.
80
+ * Call reset() before the first chunk, then decompressChunk() for each chunk.
81
+ * @param compressedData Chunk of compressed data
82
+ * @param outputSize Expected output size for this chunk
83
+ * @returns Decompressed chunk data
84
+ */
85
+ decompressChunk(compressedData: Uint8Array, outputSize: number): Uint8Array;
54
86
  /**
55
87
  * @returns Default compression level (see notes above above).
56
88
  */
@@ -1,59 +0,0 @@
1
- export type PTR = number;
2
- export interface HeapU8 {
3
- ptr: PTR;
4
- size: number;
5
- }
6
-
7
- /**
8
- * Base class to simplify moving data into and out of Wasm memory.
9
- */
10
- export class WasmLibrary {
11
-
12
- protected _module: any;
13
- protected _exports: any;
14
-
15
- protected constructor(_module: any, _export: any) {
16
- this._module = _module;
17
- this._exports = _export;
18
- }
19
-
20
- protected malloc_heapu8(size: number): HeapU8 {
21
- const ptr: PTR = this._exports.malloc(size);
22
- return {
23
- ptr,
24
- size
25
- };
26
- }
27
-
28
- protected free_heapu8(data: HeapU8) {
29
- this._exports.free(data.ptr);
30
- }
31
-
32
- protected uint8_heapu8(data: Uint8Array): HeapU8 {
33
- const retVal = this.malloc_heapu8(data.byteLength);
34
- this._module.HEAPU8.set(data, retVal.ptr);
35
- return retVal;
36
- }
37
-
38
- protected heapu8_view(data: HeapU8): Uint8Array {
39
- return this._module.HEAPU8.subarray(data.ptr, data.ptr + data.size);
40
- }
41
-
42
- protected heapu8_uint8(data: HeapU8): Uint8Array {
43
- return new Uint8Array([...this.heapu8_view(data)]);
44
- }
45
-
46
- protected string_heapu8(str: string): HeapU8 {
47
- const data = Uint8Array.from(str, x => x.charCodeAt(0));
48
- return this.uint8_heapu8(data);
49
- }
50
-
51
- protected heapu8_string(data: HeapU8): string {
52
- const retVal = Array.from({ length: data.size });
53
- const submodule = this._module.HEAPU8.subarray(data.ptr, data.ptr + data.size);
54
- submodule.forEach((c: number, i: number) => {
55
- retVal[i] = String.fromCharCode(c);
56
- });
57
- return retVal.join("");
58
- }
59
- }
@@ -1,20 +0,0 @@
1
- export type PTR = number;
2
- export interface HeapU8 {
3
- ptr: PTR;
4
- size: number;
5
- }
6
- /**
7
- * Base class to simplify moving data into and out of Wasm memory.
8
- */
9
- export declare class WasmLibrary {
10
- protected _module: any;
11
- protected _exports: any;
12
- protected constructor(_module: any, _export: any);
13
- protected malloc_heapu8(size: number): HeapU8;
14
- protected free_heapu8(data: HeapU8): void;
15
- protected uint8_heapu8(data: Uint8Array): HeapU8;
16
- protected heapu8_view(data: HeapU8): Uint8Array;
17
- protected heapu8_uint8(data: HeapU8): Uint8Array;
18
- protected string_heapu8(str: string): HeapU8;
19
- protected heapu8_string(data: HeapU8): string;
20
- }