zlib-streams 1.2.3 → 1.4.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/Makefile +3 -0
- package/README.md +40 -0
- package/dist/zlib-streams.js +31 -12
- package/package.json +1 -1
- package/src/wasm/api/zlib-streams.js +31 -12
package/Makefile
CHANGED
|
@@ -298,6 +298,9 @@ run_all_tests: dist/zlib-streams-dev.wasm
|
|
|
298
298
|
@node src/wasm/tests/test_process_error_encoding.js dist/zlib-streams-dev.wasm
|
|
299
299
|
# Buffers and zlib state released whichever end ends the stream, with no transformer hook
|
|
300
300
|
@node src/wasm/tests/test_stream_abort_releases_memory.js dist/zlib-streams-dev.wasm
|
|
301
|
+
# Bytes after the end of the stream rejected, as the platform inflaters do
|
|
302
|
+
@node src/wasm/tests/test_trailing_data.js dist/zlib-streams-dev.wasm
|
|
303
|
+
@node src/wasm/tests/test_unknown_format.js dist/zlib-streams-dev.wasm
|
|
301
304
|
@echo "Completed run_all_tests"
|
|
302
305
|
|
|
303
306
|
dist/zlib-streams-dev.wasm: $(WASM_SRCS)
|
package/README.md
CHANGED
|
@@ -42,5 +42,45 @@ Output: `dist/zip-module.wasm`
|
|
|
42
42
|
|
|
43
43
|
The files in `dist/` are tracked, so a checkout leaves them with the same timestamp as the sources and `make dist/<file>` reports them up to date even when the sources changed. The three targets above always rebuild their file; the test targets depend on the file rules and rebuild only when a source is newer. Rebuild and commit `dist/` in the same commit as a change to `src/`.
|
|
44
44
|
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
The npm package exports the JavaScript driver as `zlib-streams/zlib-streams.js` and the modules as `zlib-streams/zlib-streams.wasm` and `zlib-streams/zip-module.wasm`. The module imports nothing, so it is instantiated bare, and its exports are handed to the driver before any stream is created.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { setWasmExports, CompressionStreamZlib, DecompressionStreamZlib } from "zlib-streams/zlib-streams.js";
|
|
51
|
+
|
|
52
|
+
const wasmBytes = await (await fetch(import.meta.resolve("zlib-streams/zlib-streams.wasm"))).arrayBuffer();
|
|
53
|
+
const { instance } = await WebAssembly.instantiate(wasmBytes);
|
|
54
|
+
setWasmExports(instance.exports);
|
|
55
|
+
|
|
56
|
+
const compressed = new Blob(["Hello World"]).stream().pipeThrough(new CompressionStreamZlib("gzip", { level: 9 }));
|
|
57
|
+
const text = await new Response(compressed.pipeThrough(new DecompressionStreamZlib("gzip"))).text();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Driver functions
|
|
61
|
+
|
|
62
|
+
- `setWasmExports(exports)`: registers the exports of an instantiated module (`malloc`, `free`, `memory` and the codec functions); throws `"Invalid WASM module"` when they are missing.
|
|
63
|
+
- `resetWasmExports()`: forgets the exports; the next stream throws `"WASM module not loaded"`.
|
|
64
|
+
- `setInitError(error)`: records why the module could not be loaded; the error is the `cause` of `"WASM module not loaded"`.
|
|
65
|
+
|
|
66
|
+
### Streams
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
new CompressionStreamZlib(format = "deflate", { level, outBuffer, inBufferSize })
|
|
70
|
+
new DecompressionStreamZlib(format = "deflate", { outBuffer, inBufferSize })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Both classes return a `{ readable, writable }` pair usable with `pipeThrough()`. The formats are `"deflate"` (the zlib wrapper, RFC 1950), `"deflate-raw"` (RFC 1951, no wrapper), `"gzip"` and, for decompression only, `"deflate64-raw"`. `level` is the zlib compression level, 0 to 9, -1 (the default) for the zlib default. `outBuffer` and `inBufferSize` are the sizes in bytes of the output and input buffers allocated in the module heap, 64 KB each by default; input chunks are processed in slices of at most 32 KB.
|
|
74
|
+
|
|
75
|
+
The static properties `supportedFormats` (an array) and `requiresModule` (`true`) let a host check the formats and know that the classes are unusable until `setWasmExports()` has run.
|
|
76
|
+
|
|
77
|
+
### Errors
|
|
78
|
+
|
|
79
|
+
- Bytes following the end of a stream are rejected with `"trailing data after the end of the stream"`, whatever the format, as the `DecompressionStream` of the browsers does; concatenated gzip members are not decoded.
|
|
80
|
+
- A corrupted stream fails with `"process error:<code>"` carrying the zlib return code; a stream whose end is missing fails on close with `"end error:<code>"`.
|
|
81
|
+
- A failure to allocate memory, on construction or while processing, carries the property `code` set to `"Z_MEM_ERROR"`: `"allocation failed"` when the heap is exhausted, `"init failed:-4"` or `"process error:-4"` when zlib reports it.
|
|
82
|
+
|
|
83
|
+
The heap of the module is fixed, so the number of streams alive at the same time is bounded by their buffers; a stream releases its buffers when it closes, errors or is cancelled.
|
|
84
|
+
|
|
45
85
|
## License
|
|
46
86
|
See LICENSE for details.
|
package/dist/zlib-streams.js
CHANGED
|
@@ -32,6 +32,10 @@ const FORMAT_DEFLATE = "deflate";
|
|
|
32
32
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
33
33
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
34
34
|
const FORMAT_GZIP = "gzip";
|
|
35
|
+
const Z_MEM_ERROR = -4;
|
|
36
|
+
const Z_MEM_ERROR_CODE = "Z_MEM_ERROR";
|
|
37
|
+
const COMPRESSION_FORMATS = [FORMAT_DEFLATE, FORMAT_DEFLATE_RAW, FORMAT_GZIP];
|
|
38
|
+
const DECOMPRESSION_FORMATS = [FORMAT_DEFLATE, FORMAT_DEFLATE_RAW, FORMAT_GZIP, FORMAT_DEFLATE64_RAW];
|
|
35
39
|
|
|
36
40
|
let wasm, malloc, free, memory, initError;
|
|
37
41
|
|
|
@@ -52,7 +56,17 @@ export function resetWasmExports() {
|
|
|
52
56
|
wasm = malloc = free = memory = initError = null;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function setZlibCode(error, result) {
|
|
60
|
+
if (result === Z_MEM_ERROR) {
|
|
61
|
+
error.code = Z_MEM_ERROR_CODE;
|
|
62
|
+
}
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
function _make(isCompress, type, options = {}) {
|
|
67
|
+
if (!(isCompress ? COMPRESSION_FORMATS : DECOMPRESSION_FORMATS).includes(type)) {
|
|
68
|
+
throw new TypeError("Unsupported format: " + type);
|
|
69
|
+
}
|
|
56
70
|
if (!wasm) {
|
|
57
71
|
const error = new Error("WASM module not loaded");
|
|
58
72
|
error.cause = initError;
|
|
@@ -61,7 +75,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
61
75
|
const level = (typeof options.level === "number") ? options.level : -1;
|
|
62
76
|
const outBufferSize = (typeof options.outBuffer === "number") ? options.outBuffer : 64 * 1024;
|
|
63
77
|
const inBufferSize = (typeof options.inBufferSize === "number") ? options.inBufferSize : 64 * 1024;
|
|
64
|
-
const state = { out: 0, in: 0, inBufferSize: 0, streamHandle: 0 };
|
|
78
|
+
const state = { out: 0, in: 0, inBufferSize: 0, streamHandle: 0, streamEnded: false };
|
|
65
79
|
let readableController, writableController, resolveWrite, cancelReason;
|
|
66
80
|
let backpressure = true;
|
|
67
81
|
let cancelled = false;
|
|
@@ -114,7 +128,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
114
128
|
}
|
|
115
129
|
const result = disposeStream(state);
|
|
116
130
|
if (result !== 0) {
|
|
117
|
-
const error = new Error("end error:" + result);
|
|
131
|
+
const error = setZlibCode(new Error("end error:" + result), result);
|
|
118
132
|
readableController.error(error);
|
|
119
133
|
throw error;
|
|
120
134
|
}
|
|
@@ -134,7 +148,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
134
148
|
state.in = malloc(inBufferSize);
|
|
135
149
|
state.inBufferSize = inBufferSize;
|
|
136
150
|
if (!state.out || !state.in) {
|
|
137
|
-
throw new Error("allocation failed");
|
|
151
|
+
throw setZlibCode(new Error("allocation failed"), Z_MEM_ERROR);
|
|
138
152
|
}
|
|
139
153
|
if (isCompress) {
|
|
140
154
|
state._process = wasm.deflate_process;
|
|
@@ -170,7 +184,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
170
184
|
}
|
|
171
185
|
}
|
|
172
186
|
if (result !== 0) {
|
|
173
|
-
throw new Error("init failed:" + result);
|
|
187
|
+
throw setZlibCode(new Error("init failed:" + result), result);
|
|
174
188
|
}
|
|
175
189
|
} catch (error) {
|
|
176
190
|
disposeStream(state);
|
|
@@ -185,6 +199,9 @@ function _make(isCompress, type, options = {}) {
|
|
|
185
199
|
const out = state.out;
|
|
186
200
|
let offset = 0;
|
|
187
201
|
while (offset < chunk.length) {
|
|
202
|
+
if (state.streamEnded) {
|
|
203
|
+
throw new Error("trailing data after the end of the stream");
|
|
204
|
+
}
|
|
188
205
|
const toRead = Math.min(chunk.length - offset, 32 * 1024);
|
|
189
206
|
if (!state.in || state.inBufferSize < toRead) {
|
|
190
207
|
if (state.in && free) {
|
|
@@ -194,7 +211,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
194
211
|
state.in = malloc(toRead);
|
|
195
212
|
state.inBufferSize = toRead;
|
|
196
213
|
if (!state.in) {
|
|
197
|
-
throw new Error("allocation failed");
|
|
214
|
+
throw setZlibCode(new Error("allocation failed"), Z_MEM_ERROR);
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
217
|
heap.set(chunk.subarray(offset, offset + toRead), state.in);
|
|
@@ -203,14 +220,16 @@ function _make(isCompress, type, options = {}) {
|
|
|
203
220
|
const code = (result >> 24) & 0xff;
|
|
204
221
|
const signedCode = (code & 0x80) ? code - 256 : code;
|
|
205
222
|
if (signedCode < 0) {
|
|
206
|
-
throw new Error("process error:" + signedCode);
|
|
223
|
+
throw setZlibCode(new Error("process error:" + signedCode), signedCode);
|
|
207
224
|
}
|
|
208
225
|
const prod = result & 0x00ffffff;
|
|
209
226
|
if (prod) {
|
|
210
227
|
enqueue(heap.slice(out, out + prod));
|
|
211
228
|
}
|
|
212
229
|
const consumed = last_consumed(state.streamHandle);
|
|
213
|
-
if (
|
|
230
|
+
if (code === 1) {
|
|
231
|
+
state.streamEnded = true;
|
|
232
|
+
} else if (consumed === 0 && prod === 0) {
|
|
214
233
|
break;
|
|
215
234
|
}
|
|
216
235
|
offset += consumed;
|
|
@@ -226,7 +245,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
226
245
|
const code = (result >> 24) & 0xff;
|
|
227
246
|
const signedCode = (code & 0x80) ? code - 256 : code;
|
|
228
247
|
if (signedCode < 0) {
|
|
229
|
-
throw new Error("process error:" + signedCode);
|
|
248
|
+
throw setZlibCode(new Error("process error:" + signedCode), signedCode);
|
|
230
249
|
}
|
|
231
250
|
const produced = result & 0x00ffffff;
|
|
232
251
|
if (produced) {
|
|
@@ -285,7 +304,7 @@ export class DecompressionStreamZlib {
|
|
|
285
304
|
CompressionStreamZlib.requiresModule = true;
|
|
286
305
|
DecompressionStreamZlib.requiresModule = true;
|
|
287
306
|
// Constructing these classes before the module is loaded throws, so capability probes cannot rely
|
|
288
|
-
// on trying the constructor; the formats are declared instead,
|
|
289
|
-
//
|
|
290
|
-
CompressionStreamZlib.supportedFormats =
|
|
291
|
-
DecompressionStreamZlib.supportedFormats =
|
|
307
|
+
// on trying the constructor; the formats are declared instead, the lists _make() checks a format
|
|
308
|
+
// against before anything else.
|
|
309
|
+
CompressionStreamZlib.supportedFormats = COMPRESSION_FORMATS;
|
|
310
|
+
DecompressionStreamZlib.supportedFormats = DECOMPRESSION_FORMATS;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "WASM-based Compression Streams API implementation using zlib, with support for deflate64 decompression.",
|
|
4
4
|
"author": "Gildas Lormeau",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.4.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"deflate",
|
|
@@ -32,6 +32,10 @@ const FORMAT_DEFLATE = "deflate";
|
|
|
32
32
|
const FORMAT_DEFLATE_RAW = "deflate-raw";
|
|
33
33
|
const FORMAT_DEFLATE64_RAW = "deflate64-raw";
|
|
34
34
|
const FORMAT_GZIP = "gzip";
|
|
35
|
+
const Z_MEM_ERROR = -4;
|
|
36
|
+
const Z_MEM_ERROR_CODE = "Z_MEM_ERROR";
|
|
37
|
+
const COMPRESSION_FORMATS = [FORMAT_DEFLATE, FORMAT_DEFLATE_RAW, FORMAT_GZIP];
|
|
38
|
+
const DECOMPRESSION_FORMATS = [FORMAT_DEFLATE, FORMAT_DEFLATE_RAW, FORMAT_GZIP, FORMAT_DEFLATE64_RAW];
|
|
35
39
|
|
|
36
40
|
let wasm, malloc, free, memory, initError;
|
|
37
41
|
|
|
@@ -52,7 +56,17 @@ export function resetWasmExports() {
|
|
|
52
56
|
wasm = malloc = free = memory = initError = null;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function setZlibCode(error, result) {
|
|
60
|
+
if (result === Z_MEM_ERROR) {
|
|
61
|
+
error.code = Z_MEM_ERROR_CODE;
|
|
62
|
+
}
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
function _make(isCompress, type, options = {}) {
|
|
67
|
+
if (!(isCompress ? COMPRESSION_FORMATS : DECOMPRESSION_FORMATS).includes(type)) {
|
|
68
|
+
throw new TypeError("Unsupported format: " + type);
|
|
69
|
+
}
|
|
56
70
|
if (!wasm) {
|
|
57
71
|
const error = new Error("WASM module not loaded");
|
|
58
72
|
error.cause = initError;
|
|
@@ -61,7 +75,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
61
75
|
const level = (typeof options.level === "number") ? options.level : -1;
|
|
62
76
|
const outBufferSize = (typeof options.outBuffer === "number") ? options.outBuffer : 64 * 1024;
|
|
63
77
|
const inBufferSize = (typeof options.inBufferSize === "number") ? options.inBufferSize : 64 * 1024;
|
|
64
|
-
const state = { out: 0, in: 0, inBufferSize: 0, streamHandle: 0 };
|
|
78
|
+
const state = { out: 0, in: 0, inBufferSize: 0, streamHandle: 0, streamEnded: false };
|
|
65
79
|
let readableController, writableController, resolveWrite, cancelReason;
|
|
66
80
|
let backpressure = true;
|
|
67
81
|
let cancelled = false;
|
|
@@ -114,7 +128,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
114
128
|
}
|
|
115
129
|
const result = disposeStream(state);
|
|
116
130
|
if (result !== 0) {
|
|
117
|
-
const error = new Error("end error:" + result);
|
|
131
|
+
const error = setZlibCode(new Error("end error:" + result), result);
|
|
118
132
|
readableController.error(error);
|
|
119
133
|
throw error;
|
|
120
134
|
}
|
|
@@ -134,7 +148,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
134
148
|
state.in = malloc(inBufferSize);
|
|
135
149
|
state.inBufferSize = inBufferSize;
|
|
136
150
|
if (!state.out || !state.in) {
|
|
137
|
-
throw new Error("allocation failed");
|
|
151
|
+
throw setZlibCode(new Error("allocation failed"), Z_MEM_ERROR);
|
|
138
152
|
}
|
|
139
153
|
if (isCompress) {
|
|
140
154
|
state._process = wasm.deflate_process;
|
|
@@ -170,7 +184,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
170
184
|
}
|
|
171
185
|
}
|
|
172
186
|
if (result !== 0) {
|
|
173
|
-
throw new Error("init failed:" + result);
|
|
187
|
+
throw setZlibCode(new Error("init failed:" + result), result);
|
|
174
188
|
}
|
|
175
189
|
} catch (error) {
|
|
176
190
|
disposeStream(state);
|
|
@@ -185,6 +199,9 @@ function _make(isCompress, type, options = {}) {
|
|
|
185
199
|
const out = state.out;
|
|
186
200
|
let offset = 0;
|
|
187
201
|
while (offset < chunk.length) {
|
|
202
|
+
if (state.streamEnded) {
|
|
203
|
+
throw new Error("trailing data after the end of the stream");
|
|
204
|
+
}
|
|
188
205
|
const toRead = Math.min(chunk.length - offset, 32 * 1024);
|
|
189
206
|
if (!state.in || state.inBufferSize < toRead) {
|
|
190
207
|
if (state.in && free) {
|
|
@@ -194,7 +211,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
194
211
|
state.in = malloc(toRead);
|
|
195
212
|
state.inBufferSize = toRead;
|
|
196
213
|
if (!state.in) {
|
|
197
|
-
throw new Error("allocation failed");
|
|
214
|
+
throw setZlibCode(new Error("allocation failed"), Z_MEM_ERROR);
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
217
|
heap.set(chunk.subarray(offset, offset + toRead), state.in);
|
|
@@ -203,14 +220,16 @@ function _make(isCompress, type, options = {}) {
|
|
|
203
220
|
const code = (result >> 24) & 0xff;
|
|
204
221
|
const signedCode = (code & 0x80) ? code - 256 : code;
|
|
205
222
|
if (signedCode < 0) {
|
|
206
|
-
throw new Error("process error:" + signedCode);
|
|
223
|
+
throw setZlibCode(new Error("process error:" + signedCode), signedCode);
|
|
207
224
|
}
|
|
208
225
|
const prod = result & 0x00ffffff;
|
|
209
226
|
if (prod) {
|
|
210
227
|
enqueue(heap.slice(out, out + prod));
|
|
211
228
|
}
|
|
212
229
|
const consumed = last_consumed(state.streamHandle);
|
|
213
|
-
if (
|
|
230
|
+
if (code === 1) {
|
|
231
|
+
state.streamEnded = true;
|
|
232
|
+
} else if (consumed === 0 && prod === 0) {
|
|
214
233
|
break;
|
|
215
234
|
}
|
|
216
235
|
offset += consumed;
|
|
@@ -226,7 +245,7 @@ function _make(isCompress, type, options = {}) {
|
|
|
226
245
|
const code = (result >> 24) & 0xff;
|
|
227
246
|
const signedCode = (code & 0x80) ? code - 256 : code;
|
|
228
247
|
if (signedCode < 0) {
|
|
229
|
-
throw new Error("process error:" + signedCode);
|
|
248
|
+
throw setZlibCode(new Error("process error:" + signedCode), signedCode);
|
|
230
249
|
}
|
|
231
250
|
const produced = result & 0x00ffffff;
|
|
232
251
|
if (produced) {
|
|
@@ -285,7 +304,7 @@ export class DecompressionStreamZlib {
|
|
|
285
304
|
CompressionStreamZlib.requiresModule = true;
|
|
286
305
|
DecompressionStreamZlib.requiresModule = true;
|
|
287
306
|
// Constructing these classes before the module is loaded throws, so capability probes cannot rely
|
|
288
|
-
// on trying the constructor; the formats are declared instead,
|
|
289
|
-
//
|
|
290
|
-
CompressionStreamZlib.supportedFormats =
|
|
291
|
-
DecompressionStreamZlib.supportedFormats =
|
|
307
|
+
// on trying the constructor; the formats are declared instead, the lists _make() checks a format
|
|
308
|
+
// against before anything else.
|
|
309
|
+
CompressionStreamZlib.supportedFormats = COMPRESSION_FORMATS;
|
|
310
|
+
DecompressionStreamZlib.supportedFormats = DECOMPRESSION_FORMATS;
|