zstd-stream 1.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.
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/dist/compressor.d.ts +27 -0
- package/dist/compressor.js +155 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +148 -0
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +2 -0
- package/dist/loader.d.ts +3 -0
- package/dist/loader.js +106 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CrellinCreative
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# zstd-stream
|
|
4
|
+
|
|
5
|
+
**High-performance Zstandard compression for Node.js and browsers with zero external dependencies**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/zstd-stream)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- 🚀 **Universal compatibility** - Works seamlessly in Node.js 18+ and modern browsers
|
|
18
|
+
- 📦 **Zero external assets** - All WebAssembly code bundled internally, works out of the box
|
|
19
|
+
- 🌊 **True streaming support** - Handle multi-GB files with minimal memory footprint
|
|
20
|
+
- ⚡ **Backpressure handling** - Efficient memory management prevents overflow
|
|
21
|
+
- 🎯 **Client-side optimization** - Reduce server load by compressing/decompressing in the browser
|
|
22
|
+
- 🔧 **ESM-first** - Modern ECMAScript modules with full TypeScript support
|
|
23
|
+
- 📊 **Progress tracking** - Monitor compression/decompression in real-time
|
|
24
|
+
|
|
25
|
+
Built on the latest Zstandard v2 compression algorithm, `zstd-stream` is one of the only packages that embeds all WASM code internally, eliminating asset management headaches and enabling true plug-and-play compression.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install zstd-stream
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Usage Examples
|
|
38
|
+
|
|
39
|
+
### Basic Text Compression
|
|
40
|
+
|
|
41
|
+
**⚠️ Note:** This method loads all data into memory. For large files (>100MB), use streaming instead.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { compress, decompress } from "zstd-stream";
|
|
45
|
+
|
|
46
|
+
// Compress text
|
|
47
|
+
const text = "Hello, world!";
|
|
48
|
+
const input = new TextEncoder().encode(text);
|
|
49
|
+
const compressed = await compress(input, { level: 3 });
|
|
50
|
+
|
|
51
|
+
// Decompress
|
|
52
|
+
const decompressed = await decompress(compressed);
|
|
53
|
+
const output = new TextDecoder().decode(decompressed);
|
|
54
|
+
|
|
55
|
+
console.log(output); // "Hello, world!"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Streaming Large Files (Recommended)
|
|
59
|
+
|
|
60
|
+
**✅ Use this for large files** - Processes data in chunks with constant memory usage.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { compressStream, decompressStream } from "zstd-stream";
|
|
64
|
+
|
|
65
|
+
// Compress a stream
|
|
66
|
+
const textStream = new ReadableStream({
|
|
67
|
+
start(controller) {
|
|
68
|
+
controller.enqueue(new TextEncoder().encode("Large text data..."));
|
|
69
|
+
controller.close();
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const compressedStream = await compressStream(textStream, { level: 5 });
|
|
74
|
+
|
|
75
|
+
// Decompress a stream
|
|
76
|
+
const decompressedStream = await decompressStream(compressedStream);
|
|
77
|
+
|
|
78
|
+
// Read the result
|
|
79
|
+
const reader = decompressedStream.getReader();
|
|
80
|
+
let result = "";
|
|
81
|
+
|
|
82
|
+
while (true) {
|
|
83
|
+
const { done, value } = await reader.read();
|
|
84
|
+
if (done) break;
|
|
85
|
+
result += new TextDecoder().decode(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(result);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Browser: Download Compressed File with StreamSaver.js
|
|
92
|
+
|
|
93
|
+
**⚠️ Performance Note:** Handle compression via Web Workers for browser deployment to avoid degrading the application's performance!
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { compressStream } from "zstd-stream";
|
|
97
|
+
import streamSaver from "streamsaver";
|
|
98
|
+
|
|
99
|
+
// User selects a file
|
|
100
|
+
const file = document.querySelector('input[type="file"]').files[0];
|
|
101
|
+
const fileStream = file.stream();
|
|
102
|
+
|
|
103
|
+
// Compress the file
|
|
104
|
+
const compressed = await compressStream(fileStream, {
|
|
105
|
+
level: 9,
|
|
106
|
+
onProgress: (bytes) => {
|
|
107
|
+
console.log(`Compressed: ${(bytes / 1024 / 1024).toFixed(2)} MB`);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Save to disk as .zst
|
|
112
|
+
const fileWriteStream = streamSaver.createWriteStream(`${file.name}.zst`);
|
|
113
|
+
const writer = fileWriteStream.getWriter();
|
|
114
|
+
|
|
115
|
+
const reader = compressed.getReader();
|
|
116
|
+
while (true) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) break;
|
|
119
|
+
await writer.write(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await writer.close();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Stream File to Server via HTTP
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { compressStream } from "zstd-stream";
|
|
129
|
+
|
|
130
|
+
// Get file from user input
|
|
131
|
+
const file = document.querySelector('input[type="file"]').files[0];
|
|
132
|
+
const fileStream = file.stream();
|
|
133
|
+
|
|
134
|
+
// Compress and upload
|
|
135
|
+
const compressed = await compressStream(fileStream, { level: 6 });
|
|
136
|
+
|
|
137
|
+
const response = await fetch("https://api.example.com/upload", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/zstd",
|
|
141
|
+
"Content-Encoding": "zstd",
|
|
142
|
+
},
|
|
143
|
+
body: compressed,
|
|
144
|
+
duplex: "half", // Required for streaming request bodies
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (response.ok) {
|
|
148
|
+
console.log("Upload complete!");
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### `compress(input, options?)`
|
|
157
|
+
|
|
158
|
+
Compress data in one operation. Best for small files.
|
|
159
|
+
|
|
160
|
+
**Parameters:**
|
|
161
|
+
|
|
162
|
+
- `input: Uint8Array` - Data to compress
|
|
163
|
+
- `options?: CompressOptions`
|
|
164
|
+
- `level?: number` - Compression level (1-19, default: 3)
|
|
165
|
+
- `onProgress?: (bytesWritten: number) => void` - Progress callback
|
|
166
|
+
|
|
167
|
+
**Returns:** `Promise<Uint8Array>`
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const compressed = await compress(data, { level: 9 });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `compressStream(input, options?)`
|
|
174
|
+
|
|
175
|
+
Compress a readable stream. Best for large files.
|
|
176
|
+
|
|
177
|
+
**Parameters:**
|
|
178
|
+
|
|
179
|
+
- `input: ReadableStream<Uint8Array>` - Stream to compress
|
|
180
|
+
- `options?: CompressOptions`
|
|
181
|
+
- `level?: number` - Compression level (1-19, default: 3)
|
|
182
|
+
- `onProgress?: (bytesWritten: number) => void` - Progress callback
|
|
183
|
+
|
|
184
|
+
**Returns:** `Promise<ReadableStream<Uint8Array>>`
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const compressed = await compressStream(fileStream, { level: 5 });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `decompress(input, options?)`
|
|
191
|
+
|
|
192
|
+
Decompress data in one operation.
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
|
|
196
|
+
- `input: Uint8Array` - Compressed data
|
|
197
|
+
- `options?: DecompressOptions`
|
|
198
|
+
- `onProgress?: (bytesWritten: number) => void` - Progress callback
|
|
199
|
+
|
|
200
|
+
**Returns:** `Promise<Uint8Array>`
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const decompressed = await decompress(compressed);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `decompressStream(input, options?)`
|
|
207
|
+
|
|
208
|
+
Decompress a readable stream with backpressure support.
|
|
209
|
+
|
|
210
|
+
**Parameters:**
|
|
211
|
+
|
|
212
|
+
- `input: ReadableStream<Uint8Array>` - Compressed stream
|
|
213
|
+
- `options?: DecompressOptions`
|
|
214
|
+
- `onProgress?: (bytesWritten: number) => void` - Progress callback
|
|
215
|
+
|
|
216
|
+
**Returns:** `Promise<ReadableStream<Uint8Array>>`
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const decompressed = await decompressStream(compressedStream);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `initialize()`
|
|
223
|
+
|
|
224
|
+
Pre-initialize the WASM module (optional). Call during app startup to avoid initialization delay on first use.
|
|
225
|
+
|
|
226
|
+
**Returns:** `Promise<void>`
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
await initialize();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Compression Levels
|
|
235
|
+
|
|
236
|
+
Levels 1-19 are supported. Higher levels provide diminishing returns.
|
|
237
|
+
|
|
238
|
+
| Level | Speed | Ratio | Use Case |
|
|
239
|
+
| ----- | --------- | -------- | ---------------------------------- |
|
|
240
|
+
| 1-3 | Fast | Lower | Real-time, network streaming |
|
|
241
|
+
| 3-7 | Medium | Balanced | General purpose (recommended) |
|
|
242
|
+
| 8-15 | Slow | Better | File storage, archival |
|
|
243
|
+
| 16-19 | Very slow | Maximum | One-time compression, cold storage |
|
|
244
|
+
|
|
245
|
+
**Default level:** 3 (optimal balance of speed and compression)
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Browser Compatibility
|
|
250
|
+
|
|
251
|
+
- Chrome/Edge 80+
|
|
252
|
+
- Firefox 113+
|
|
253
|
+
- Safari 16.4+
|
|
254
|
+
- Node.js 18+
|
|
255
|
+
|
|
256
|
+
Requires WebAssembly and ES2022 support.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## TypeScript
|
|
261
|
+
|
|
262
|
+
Full type definitions included:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import type { CompressOptions, DecompressOptions } from "zstd-stream";
|
|
266
|
+
|
|
267
|
+
const options: CompressOptions = {
|
|
268
|
+
level: 9,
|
|
269
|
+
onProgress: (bytes) => console.log(`Progress: ${bytes}`),
|
|
270
|
+
};
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Credits
|
|
282
|
+
|
|
283
|
+
Built with [Zstandard](https://github.com/facebook/zstd) by Meta, compiled using [Emscripten SDK](https://emscripten.org/).
|
|
284
|
+
|
|
285
|
+
WebAssembly module embedded internally for zero-dependency deployment.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Module } from "./types.js";
|
|
2
|
+
declare abstract class BaseProcessor {
|
|
3
|
+
protected destroyed: boolean;
|
|
4
|
+
protected module: Module | null;
|
|
5
|
+
destroy(): void;
|
|
6
|
+
protected abstract cleanup(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare class ZstdCompressor extends BaseProcessor {
|
|
9
|
+
private level;
|
|
10
|
+
private ctx;
|
|
11
|
+
private buffer;
|
|
12
|
+
private bufferSize;
|
|
13
|
+
constructor(level: number);
|
|
14
|
+
init(): Promise<void>;
|
|
15
|
+
process(data: Uint8Array, isLast: boolean): Uint8Array;
|
|
16
|
+
protected cleanup(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare class ZstdDecompressor extends BaseProcessor {
|
|
19
|
+
private ctx;
|
|
20
|
+
private buffer;
|
|
21
|
+
private bufferSize;
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
process(data: Uint8Array): Uint8Array;
|
|
24
|
+
private concat;
|
|
25
|
+
protected cleanup(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { getModule } from "./loader.js";
|
|
2
|
+
class BaseProcessor {
|
|
3
|
+
destroyed = false;
|
|
4
|
+
module = null;
|
|
5
|
+
destroy() {
|
|
6
|
+
if (!this.destroyed) {
|
|
7
|
+
this.cleanup();
|
|
8
|
+
this.destroyed = true;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class ZstdCompressor extends BaseProcessor {
|
|
13
|
+
level;
|
|
14
|
+
ctx = 0;
|
|
15
|
+
buffer = 0;
|
|
16
|
+
bufferSize = 0;
|
|
17
|
+
constructor(level) {
|
|
18
|
+
super();
|
|
19
|
+
this.level = level;
|
|
20
|
+
if (level < 1 || level > 19) {
|
|
21
|
+
throw new Error("Compression level must be between 1 and 19");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async init() {
|
|
25
|
+
this.module = getModule();
|
|
26
|
+
}
|
|
27
|
+
process(data, isLast) {
|
|
28
|
+
if (!this.module)
|
|
29
|
+
throw new Error("Not initialized");
|
|
30
|
+
if (this.destroyed)
|
|
31
|
+
throw new Error("Destroyed");
|
|
32
|
+
if (!this.ctx) {
|
|
33
|
+
this.ctx = this.module._createCCtx();
|
|
34
|
+
if (!this.ctx || this.module._initCStream(this.ctx, this.level) !== 0) {
|
|
35
|
+
this.cleanup();
|
|
36
|
+
throw new Error("Failed to create compression context");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!data.length && !isLast)
|
|
40
|
+
return new Uint8Array(0);
|
|
41
|
+
const srcPtr = this.module._malloc(data.length);
|
|
42
|
+
if (!srcPtr)
|
|
43
|
+
throw new Error("Failed to allocate source buffer");
|
|
44
|
+
this.module.HEAPU8.set(data, srcPtr);
|
|
45
|
+
try {
|
|
46
|
+
if (!this.buffer) {
|
|
47
|
+
this.bufferSize = this.module._cStreamOutSize();
|
|
48
|
+
this.buffer = this.module._malloc(this.bufferSize);
|
|
49
|
+
if (!this.buffer)
|
|
50
|
+
throw new Error("Failed to allocate output buffer");
|
|
51
|
+
}
|
|
52
|
+
const result = Number(this.module._compressStream(this.ctx, this.buffer, this.bufferSize, srcPtr, data.length, isLast ? 2 : 0));
|
|
53
|
+
if (result < 0) {
|
|
54
|
+
throw new Error(`Compression failed: ${this.module._getErrorName(-result)}`);
|
|
55
|
+
}
|
|
56
|
+
return result === 0
|
|
57
|
+
? new Uint8Array(0)
|
|
58
|
+
: new Uint8Array(this.module.HEAPU8.subarray(this.buffer, this.buffer + result));
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.module._free(srcPtr);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
cleanup() {
|
|
65
|
+
if (this.module) {
|
|
66
|
+
if (this.ctx)
|
|
67
|
+
this.module._freeCCtx(this.ctx);
|
|
68
|
+
if (this.buffer)
|
|
69
|
+
this.module._free(this.buffer);
|
|
70
|
+
this.ctx = 0;
|
|
71
|
+
this.buffer = 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class ZstdDecompressor extends BaseProcessor {
|
|
76
|
+
ctx = 0;
|
|
77
|
+
buffer = 0;
|
|
78
|
+
bufferSize = 0;
|
|
79
|
+
async init() {
|
|
80
|
+
this.module = getModule();
|
|
81
|
+
}
|
|
82
|
+
process(data) {
|
|
83
|
+
if (!this.module)
|
|
84
|
+
throw new Error("Not initialized");
|
|
85
|
+
if (this.destroyed)
|
|
86
|
+
throw new Error("Destroyed");
|
|
87
|
+
if (!this.ctx) {
|
|
88
|
+
this.ctx = this.module._createDCtx();
|
|
89
|
+
if (!this.ctx || this.module._initDStream(this.ctx) === 0) {
|
|
90
|
+
this.cleanup();
|
|
91
|
+
throw new Error("Failed to create decompression context");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!data.length)
|
|
95
|
+
return new Uint8Array(0);
|
|
96
|
+
if (!this.buffer) {
|
|
97
|
+
this.bufferSize = this.module._dStreamOutSize();
|
|
98
|
+
this.buffer = this.module._malloc(this.bufferSize);
|
|
99
|
+
if (!this.buffer)
|
|
100
|
+
throw new Error("Failed to allocate output buffer");
|
|
101
|
+
}
|
|
102
|
+
const srcPtr = this.module._malloc(data.length);
|
|
103
|
+
if (!srcPtr)
|
|
104
|
+
throw new Error("Failed to allocate source buffer");
|
|
105
|
+
this.module.HEAPU8.set(data, srcPtr);
|
|
106
|
+
try {
|
|
107
|
+
let srcPos = 0;
|
|
108
|
+
const chunks = [];
|
|
109
|
+
while (srcPos < data.length) {
|
|
110
|
+
const result = this.module._decompressStream(this.ctx, this.buffer, this.bufferSize, srcPtr + srcPos, data.length - srcPos);
|
|
111
|
+
if (result < 0) {
|
|
112
|
+
const errorCode = Number(BigInt(result) & 0x7fffffffffffffffn);
|
|
113
|
+
throw new Error(`Decompression failed: ${this.module._getErrorName(errorCode)}`);
|
|
114
|
+
}
|
|
115
|
+
const consumed = Number(BigInt(result) >> 32n);
|
|
116
|
+
const outputSize = Number(BigInt(result) & 0xffffffffn);
|
|
117
|
+
if (consumed === 0 && outputSize === 0) {
|
|
118
|
+
throw new Error("Decompression stalled");
|
|
119
|
+
}
|
|
120
|
+
if (outputSize > 0) {
|
|
121
|
+
chunks.push(new Uint8Array(this.module.HEAPU8.subarray(this.buffer, this.buffer + outputSize)));
|
|
122
|
+
}
|
|
123
|
+
srcPos += consumed;
|
|
124
|
+
}
|
|
125
|
+
return this.concat(chunks);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
this.module._free(srcPtr);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
concat(chunks) {
|
|
132
|
+
if (chunks.length === 0)
|
|
133
|
+
return new Uint8Array(0);
|
|
134
|
+
if (chunks.length === 1)
|
|
135
|
+
return chunks[0];
|
|
136
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
137
|
+
const result = new Uint8Array(total);
|
|
138
|
+
let offset = 0;
|
|
139
|
+
for (const chunk of chunks) {
|
|
140
|
+
result.set(chunk, offset);
|
|
141
|
+
offset += chunk.length;
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
cleanup() {
|
|
146
|
+
if (this.module) {
|
|
147
|
+
if (this.ctx)
|
|
148
|
+
this.module._freeDCtx(this.ctx);
|
|
149
|
+
if (this.buffer)
|
|
150
|
+
this.module._free(this.buffer);
|
|
151
|
+
this.ctx = 0;
|
|
152
|
+
this.buffer = 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CompressOptions {
|
|
2
|
+
level?: number;
|
|
3
|
+
onProgress?: (bytesWritten: number) => void;
|
|
4
|
+
}
|
|
5
|
+
export interface DecompressOptions {
|
|
6
|
+
onProgress?: (bytesWritten: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function compress(input: Uint8Array, options?: CompressOptions): Promise<Uint8Array>;
|
|
9
|
+
export declare function compressStream(input: ReadableStream<Uint8Array>, options?: CompressOptions): Promise<ReadableStream<Uint8Array>>;
|
|
10
|
+
export declare function decompress(input: Uint8Array, options?: DecompressOptions): Promise<Uint8Array>;
|
|
11
|
+
export declare function decompressStream(input: ReadableStream<Uint8Array>, options?: DecompressOptions): Promise<ReadableStream<Uint8Array>>;
|
|
12
|
+
export declare function initialize(): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ZstdCompressor, ZstdDecompressor } from "./compressor.js";
|
|
2
|
+
import { initZstd } from "./loader.js";
|
|
3
|
+
let initialized = false;
|
|
4
|
+
async function ensureInit() {
|
|
5
|
+
if (!initialized) {
|
|
6
|
+
await initZstd();
|
|
7
|
+
initialized = true;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
// Compress Uint8Array -> Uint8Array
|
|
11
|
+
export async function compress(input, options) {
|
|
12
|
+
await ensureInit();
|
|
13
|
+
const { level = 3, onProgress } = options || {};
|
|
14
|
+
const compressor = new ZstdCompressor(level);
|
|
15
|
+
await compressor.init();
|
|
16
|
+
try {
|
|
17
|
+
const result = compressor.process(input, true);
|
|
18
|
+
onProgress?.(result.length);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
compressor.destroy();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function compressStream(input, options) {
|
|
26
|
+
await ensureInit();
|
|
27
|
+
const { level = 3, onProgress } = options || {};
|
|
28
|
+
const reader = input.getReader();
|
|
29
|
+
let compressor = null;
|
|
30
|
+
let totalWritten = 0;
|
|
31
|
+
let inputDone = false;
|
|
32
|
+
return new ReadableStream({
|
|
33
|
+
async start() {
|
|
34
|
+
compressor = new ZstdCompressor(level);
|
|
35
|
+
await compressor.init();
|
|
36
|
+
},
|
|
37
|
+
async pull(controller) {
|
|
38
|
+
try {
|
|
39
|
+
if (inputDone)
|
|
40
|
+
return;
|
|
41
|
+
let loopIterations = 0;
|
|
42
|
+
// Keep reading until we have data to enqueue or input is exhausted
|
|
43
|
+
while (!inputDone) {
|
|
44
|
+
loopIterations++;
|
|
45
|
+
const { done, value } = await reader.read();
|
|
46
|
+
if (done) {
|
|
47
|
+
// Final flush
|
|
48
|
+
const chunk = compressor.process(new Uint8Array(0), true);
|
|
49
|
+
if (chunk.length > 0) {
|
|
50
|
+
controller.enqueue(chunk);
|
|
51
|
+
totalWritten += chunk.length;
|
|
52
|
+
onProgress?.(totalWritten);
|
|
53
|
+
}
|
|
54
|
+
controller.close();
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
compressor?.destroy();
|
|
57
|
+
inputDone = true;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Process chunk
|
|
61
|
+
const chunk = compressor.process(value, false);
|
|
62
|
+
if (chunk.length > 0) {
|
|
63
|
+
controller.enqueue(chunk);
|
|
64
|
+
totalWritten += chunk.length;
|
|
65
|
+
onProgress?.(totalWritten);
|
|
66
|
+
return; // Exit after enqueuing data
|
|
67
|
+
}
|
|
68
|
+
// If chunk is empty, continue loop to read next input chunk
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
inputDone = true;
|
|
73
|
+
reader.releaseLock();
|
|
74
|
+
compressor?.destroy();
|
|
75
|
+
controller.error(error);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
cancel() {
|
|
79
|
+
reader.releaseLock();
|
|
80
|
+
compressor?.destroy();
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Decompress Uint8Array -> Uint8Array
|
|
85
|
+
export async function decompress(input, options) {
|
|
86
|
+
await ensureInit();
|
|
87
|
+
const { onProgress } = options || {};
|
|
88
|
+
const decompressor = new ZstdDecompressor();
|
|
89
|
+
await decompressor.init();
|
|
90
|
+
try {
|
|
91
|
+
const result = decompressor.process(input);
|
|
92
|
+
onProgress?.(result.length);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
decompressor.destroy();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Decompress ReadableStream -> ReadableStream with backpressure
|
|
100
|
+
export async function decompressStream(input, options) {
|
|
101
|
+
await ensureInit();
|
|
102
|
+
const { onProgress } = options || {};
|
|
103
|
+
const reader = input.getReader();
|
|
104
|
+
let decompressor = null;
|
|
105
|
+
let totalWritten = 0;
|
|
106
|
+
let inputDone = false;
|
|
107
|
+
return new ReadableStream({
|
|
108
|
+
async start() {
|
|
109
|
+
decompressor = new ZstdDecompressor();
|
|
110
|
+
await decompressor.init();
|
|
111
|
+
},
|
|
112
|
+
async pull(controller) {
|
|
113
|
+
try {
|
|
114
|
+
if (inputDone) {
|
|
115
|
+
return; // Already finished
|
|
116
|
+
}
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) {
|
|
119
|
+
inputDone = true;
|
|
120
|
+
controller.close();
|
|
121
|
+
reader.releaseLock();
|
|
122
|
+
decompressor?.destroy();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const chunk = decompressor.process(value);
|
|
126
|
+
if (chunk.length > 0) {
|
|
127
|
+
controller.enqueue(chunk);
|
|
128
|
+
totalWritten += chunk.length;
|
|
129
|
+
onProgress?.(totalWritten);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
inputDone = true;
|
|
134
|
+
reader.releaseLock();
|
|
135
|
+
decompressor?.destroy();
|
|
136
|
+
controller.error(error);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
cancel() {
|
|
141
|
+
reader.releaseLock();
|
|
142
|
+
decompressor?.destroy();
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export async function initialize() {
|
|
147
|
+
await ensureInit();
|
|
148
|
+
}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const lib: string;
|