@addmaple/gzip 0.1.0 → 0.1.1

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.
@@ -1,2 +1,5 @@
1
- export function init(imports?: WebAssembly.Imports): Promise<void>;
1
+ export interface InitOptions {
2
+ backend?: 'auto' | 'simd' | 'base';
3
+ }
4
+ export function init(imports?: WebAssembly.Imports, opts?: InitOptions): Promise<void>;
2
5
  export * from "./custom.js";
@@ -1,19 +1,26 @@
1
1
  import { setInstance, registerInit } from "./core.js";
2
- import { instantiateWithFallback } from "./util.js";
2
+ import { instantiateWithBackend } from "./util.js";
3
3
 
4
- import { wasmBytes as simdBytes } from "./wasm-inline/gzip.simd.wasm.js";
5
- import { wasmBytes as baseBytes } from "./wasm-inline/gzip.base.wasm.js";
4
+ import { wasmBytes as _simdBytes } from "./wasm-inline/gzip.simd.wasm.js";
5
+ import { wasmBytes as _baseBytes } from "./wasm-inline/gzip.base.wasm.js";
6
6
 
7
- async function getWasmBytes() {
8
- return { simdBytes, baseBytes };
7
+ async function getSimdBytes() {
8
+ return _simdBytes;
9
+ }
10
+
11
+ async function getBaseBytes() {
12
+ return _baseBytes;
9
13
  }
10
14
 
11
15
 
12
16
  let _ready = null;
13
- export function init(imports = {}) {
14
- return (_ready ??= (async () => {
15
- const { simdBytes, baseBytes } = await getWasmBytes();
16
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
17
+ let _backend = null;
18
+ export function init(imports = {}, opts = {}) {
19
+ const backend = opts.backend || 'auto';
20
+ if (_ready && _backend === backend) return _ready;
21
+ _backend = backend;
22
+ return (_ready = (async () => {
23
+ const { instance } = await instantiateWithBackend({ getSimdBytes, getBaseBytes, imports, backend });
17
24
  setInstance(instance);
18
25
  })());
19
26
  }
package/dist/browser.d.ts CHANGED
@@ -1,2 +1,5 @@
1
- export function init(imports?: WebAssembly.Imports): Promise<void>;
1
+ export interface InitOptions {
2
+ backend?: 'auto' | 'simd' | 'base';
3
+ }
4
+ export function init(imports?: WebAssembly.Imports, opts?: InitOptions): Promise<void>;
2
5
  export * from "./custom.js";
package/dist/browser.js CHANGED
@@ -1,21 +1,28 @@
1
1
  import { setInstance, registerInit } from "./core.js";
2
- import { instantiateWithFallback } from "./util.js";
2
+ import { instantiateWithBackend } from "./util.js";
3
3
 
4
4
  const simdUrl = new URL("./wasm/gzip.simd.wasm", import.meta.url);
5
5
  const baseUrl = new URL("./wasm/gzip.base.wasm", import.meta.url);
6
6
 
7
- async function getWasmBytes() {
8
- const [simdRes, baseRes] = await Promise.all([fetch(simdUrl), fetch(baseUrl)]);
9
- const [simdBytes, baseBytes] = await Promise.all([simdRes.arrayBuffer(), baseRes.arrayBuffer()]);
10
- return { simdBytes, baseBytes };
7
+ async function getSimdBytes() {
8
+ const res = await fetch(simdUrl);
9
+ return res.arrayBuffer();
10
+ }
11
+
12
+ async function getBaseBytes() {
13
+ const res = await fetch(baseUrl);
14
+ return res.arrayBuffer();
11
15
  }
12
16
 
13
17
 
14
18
  let _ready = null;
15
- export function init(imports = {}) {
16
- return (_ready ??= (async () => {
17
- const { simdBytes, baseBytes } = await getWasmBytes();
18
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
19
+ let _backend = null;
20
+ export function init(imports = {}, opts = {}) {
21
+ const backend = opts.backend || 'auto';
22
+ if (_ready && _backend === backend) return _ready;
23
+ _backend = backend;
24
+ return (_ready = (async () => {
25
+ const { instance } = await instantiateWithBackend({ getSimdBytes, getBaseBytes, imports, backend });
19
26
  setInstance(instance);
20
27
  })());
21
28
  }
package/dist/core.d.ts CHANGED
@@ -8,4 +8,8 @@ export function free(ptr: number, len: number): void;
8
8
 
9
9
  export function compress_level_1(input: WasmInput): Promise<Uint8Array>;
10
10
  export function compress_level_6(input: WasmInput): Promise<Uint8Array>;
11
- export function compress_level_9(input: WasmInput): Promise<Uint8Array>;
11
+ export function compress_level_9(input: WasmInput): Promise<Uint8Array>;
12
+ export function decompress_gzip(input: WasmInput): Promise<Uint8Array>;
13
+ export function create_gzip_compressor(input: WasmInput): Promise<number>;
14
+ export function compress_gzip_chunk(input: WasmInput): Promise<Uint8Array>;
15
+ export function destroy_gzip_compressor(input: WasmInput): Promise<Uint8Array>;
package/dist/core.js CHANGED
@@ -45,6 +45,40 @@ function toBytes(input) {
45
45
  throw new TypeError("Expected a TypedArray or ArrayBuffer");
46
46
  }
47
47
 
48
+ function scalarSize(type) {
49
+ switch (type) {
50
+ case "f64": return 8;
51
+ case "f32":
52
+ case "i32":
53
+ case "u32": return 4;
54
+ case "i16":
55
+ case "u16": return 2;
56
+ case "i8":
57
+ case "u8": return 1;
58
+ case "u32_array":
59
+ case "i32_array":
60
+ case "f32_array": return 1024 * 1024;
61
+ default: return 0;
62
+ }
63
+ }
64
+
65
+ function decodeReturn(view, type) {
66
+ switch (type) {
67
+ case "f32": return view.getFloat32(0, true);
68
+ case "f64": return view.getFloat64(0, true);
69
+ case "i32": return view.getInt32(0, true);
70
+ case "u32": return view.getUint32(0, true);
71
+ case "i16": return view.getInt16(0, true);
72
+ case "u16": return view.getUint16(0, true);
73
+ case "i8": return view.getInt8(0);
74
+ case "u8": return view.getUint8(0);
75
+ case "u32_array": return new Uint32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
76
+ case "i32_array": return new Int32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
77
+ case "f32_array": return new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
78
+ default: return null;
79
+ }
80
+ }
81
+
48
82
  function callWasm(abi, input, outLen, reuse) {
49
83
  if (!_inst) throw new Error("WASM instance not initialized");
50
84
  const view = toBytes(input);
@@ -123,3 +157,64 @@ async function compress_level_9(input) {
123
157
  return result;
124
158
  }
125
159
  export { compress_level_9 };
160
+
161
+ async function decompress_gzip(input) {
162
+ await ensureReady();
163
+ const view = toBytes(input);
164
+ const len = view.byteLength;
165
+ const outLen = len * 10;
166
+ const { outPtr, written, inPtr } = callWasm("decompress_gzip", view, outLen, null);
167
+
168
+ const result = memoryU8().slice(outPtr, outPtr + written);
169
+
170
+ free(inPtr, len);
171
+ free(outPtr, outLen);
172
+ return result;
173
+ }
174
+ export { decompress_gzip };
175
+
176
+ async function create_gzip_compressor(input) {
177
+ await ensureReady();
178
+ const view = toBytes(input);
179
+ const len = view.byteLength;
180
+ const outLen = (scalarSize('u32') || 4);
181
+ const { outPtr, written, inPtr } = callWasm("create_gzip_compressor", view, outLen, null);
182
+
183
+ const retView = new DataView(memoryU8().buffer, outPtr, written);
184
+ const result = decodeReturn(retView, "u32");
185
+
186
+ free(inPtr, len);
187
+ free(outPtr, outLen);
188
+ return result;
189
+ }
190
+ export { create_gzip_compressor };
191
+
192
+ async function compress_gzip_chunk(input) {
193
+ await ensureReady();
194
+ const view = toBytes(input);
195
+ const len = view.byteLength;
196
+ const outLen = len + 1024;
197
+ const { outPtr, written, inPtr } = callWasm("compress_gzip_chunk", view, outLen, null);
198
+
199
+ const result = memoryU8().slice(outPtr, outPtr + written);
200
+
201
+ free(inPtr, len);
202
+ free(outPtr, outLen);
203
+ return result;
204
+ }
205
+ export { compress_gzip_chunk };
206
+
207
+ async function destroy_gzip_compressor(input) {
208
+ await ensureReady();
209
+ const view = toBytes(input);
210
+ const len = view.byteLength;
211
+ const outLen = Math.max(len, 4);
212
+ const { outPtr, written, inPtr } = callWasm("destroy_gzip_compressor", view, outLen, null);
213
+
214
+ const result = memoryU8().slice(outPtr, outPtr + written);
215
+
216
+ free(inPtr, len);
217
+ free(outPtr, outLen);
218
+ return result;
219
+ }
220
+ export { destroy_gzip_compressor };
package/dist/custom.js CHANGED
@@ -1,4 +1,28 @@
1
- import { compress_level_1, compress_level_6, compress_level_9, wasmExports } from './core.js';
1
+ import {
2
+ compress_level_1,
3
+ compress_level_6,
4
+ compress_level_9,
5
+ decompress_gzip,
6
+ create_gzip_compressor,
7
+ compress_gzip_chunk,
8
+ destroy_gzip_compressor,
9
+ wasmExports,
10
+ alloc,
11
+ free,
12
+ memoryU8,
13
+ ensureReady
14
+ } from './core.js';
15
+
16
+ function toBytes(input) {
17
+ if (input instanceof Uint8Array) return input;
18
+ if (ArrayBuffer.isView(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
19
+ if (input instanceof ArrayBuffer) return new Uint8Array(input);
20
+ throw new TypeError("Expected a TypedArray or ArrayBuffer");
21
+ }
22
+
23
+ // ============================================================================
24
+ // One-shot Compression API
25
+ // ============================================================================
2
26
 
3
27
  export async function compress(input, options = {}) {
4
28
  const level = options.level ?? 6;
@@ -12,4 +36,139 @@ export async function compress(input, options = {}) {
12
36
  }
13
37
  }
14
38
 
39
+ // ============================================================================
40
+ // One-shot Decompression API
41
+ // ============================================================================
42
+
43
+ export async function decompress(input) {
44
+ await ensureReady();
45
+
46
+ const view = toBytes(input);
47
+ const len = view.byteLength;
48
+
49
+ // Start with a reasonable estimate (10x compressed size)
50
+ let outLen = Math.max(len * 10, 65536);
51
+
52
+ const inPtr = alloc(len);
53
+ let outPtr = alloc(outLen);
54
+
55
+ try {
56
+ memoryU8().set(view, inPtr);
57
+ let written = wasmExports().decompress_gzip(inPtr, len, outPtr, outLen);
58
+
59
+ // Negative value means we need more space
60
+ if (written < 0) {
61
+ const neededLen = -written;
62
+ free(outPtr, outLen);
63
+ outLen = neededLen;
64
+ outPtr = alloc(outLen);
65
+
66
+ // Retry with correct size
67
+ written = wasmExports().decompress_gzip(inPtr, len, outPtr, outLen);
68
+
69
+ if (written < 0) {
70
+ throw new Error('Decompression failed after resize');
71
+ }
72
+ }
73
+
74
+ const result = memoryU8().slice(outPtr, outPtr + written);
75
+ free(inPtr, len);
76
+ free(outPtr, outLen);
77
+ return result;
78
+ } catch (error) {
79
+ free(inPtr, len);
80
+ free(outPtr, outLen);
81
+ throw new Error(`Decompression failed: ${error.message}`);
82
+ }
83
+ }
84
+
85
+ // ============================================================================
86
+ // Streaming Compression API
87
+ // ============================================================================
88
+
89
+ export class StreamingCompressor {
90
+ constructor(options = {}) {
91
+ this._initPromise = ensureReady();
92
+ this.level = options.level ?? 6;
93
+ this.handle = null;
94
+ }
95
+
96
+ async _ensureInit() {
97
+ await this._initPromise;
98
+ if (this.handle === null) {
99
+ this.handle = wasmExports().create_gzip_compressor(this.level);
100
+ if (this.handle === 0) {
101
+ throw new Error('Failed to create compressor');
102
+ }
103
+ }
104
+ }
105
+
106
+ async compressChunk(input, finish = false) {
107
+ await this._ensureInit();
108
+ if (this.handle === 0) {
109
+ throw new Error('Compressor already destroyed');
110
+ }
111
+
112
+ const view = toBytes(input);
113
+ const len = view.byteLength;
114
+ const outLen = len + 1024;
115
+
116
+ const inPtr = alloc(len);
117
+ const outPtr = alloc(outLen);
118
+
119
+ try {
120
+ memoryU8().set(view, inPtr);
121
+ const written = wasmExports().compress_gzip_chunk(this.handle, inPtr, len, outPtr, outLen, finish ? 1 : 0);
122
+
123
+ if (written < 0) {
124
+ if (written === -1) {
125
+ throw new Error('Compression failed');
126
+ } else {
127
+ // Negative value indicates needed buffer size
128
+ free(outPtr, outLen);
129
+ const neededLen = -written;
130
+ const newOutPtr = alloc(neededLen);
131
+ memoryU8().set(view, inPtr);
132
+ const retryWritten = wasmExports().compress_gzip_chunk(this.handle, inPtr, len, newOutPtr, neededLen, finish ? 1 : 0);
133
+ if (retryWritten < 0) {
134
+ free(newOutPtr, neededLen);
135
+ throw new Error('Compression failed after retry');
136
+ }
137
+ const result = memoryU8().slice(newOutPtr, newOutPtr + retryWritten);
138
+ free(newOutPtr, neededLen);
139
+ free(inPtr, len);
140
+ if (finish) this.handle = 0;
141
+ return result;
142
+ }
143
+ }
144
+
145
+ if (written === 0) {
146
+ free(outPtr, outLen);
147
+ free(inPtr, len);
148
+ return new Uint8Array(0);
149
+ }
150
+
151
+ const result = memoryU8().slice(outPtr, outPtr + written);
152
+ free(outPtr, outLen);
153
+ free(inPtr, len);
154
+
155
+ if (finish) this.handle = 0;
156
+ return result;
157
+ } catch (error) {
158
+ free(outPtr, outLen);
159
+ free(inPtr, len);
160
+ throw new Error(`Compression failed: ${error.message}`);
161
+ }
162
+ }
163
+
164
+ async destroy() {
165
+ await this._initPromise;
166
+ if (this.handle !== 0 && this.handle !== null) {
167
+ wasmExports().destroy_gzip_compressor(this.handle);
168
+ this.handle = 0;
169
+ }
170
+ }
171
+ }
172
+
15
173
  export { wasmExports };
174
+
@@ -1,2 +1,5 @@
1
- export function init(imports?: WebAssembly.Imports): Promise<void>;
1
+ export interface InitOptions {
2
+ backend?: 'auto' | 'simd' | 'base';
3
+ }
4
+ export function init(imports?: WebAssembly.Imports, opts?: InitOptions): Promise<void>;
2
5
  export * from "./custom.js";
@@ -1,19 +1,26 @@
1
1
  import { setInstance, registerInit } from "./core.js";
2
- import { instantiateWithFallback } from "./util.js";
2
+ import { instantiateWithBackend } from "./util.js";
3
3
 
4
- import { wasmBytes as simdBytes } from "./wasm-inline/gzip.simd.wasm.js";
5
- import { wasmBytes as baseBytes } from "./wasm-inline/gzip.base.wasm.js";
4
+ import { wasmBytes as _simdBytes } from "./wasm-inline/gzip.simd.wasm.js";
5
+ import { wasmBytes as _baseBytes } from "./wasm-inline/gzip.base.wasm.js";
6
6
 
7
- async function getWasmBytes() {
8
- return { simdBytes, baseBytes };
7
+ async function getSimdBytes() {
8
+ return _simdBytes;
9
+ }
10
+
11
+ async function getBaseBytes() {
12
+ return _baseBytes;
9
13
  }
10
14
 
11
15
 
12
16
  let _ready = null;
13
- export function init(imports = {}) {
14
- return (_ready ??= (async () => {
15
- const { simdBytes, baseBytes } = await getWasmBytes();
16
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
17
+ let _backend = null;
18
+ export function init(imports = {}, opts = {}) {
19
+ const backend = opts.backend || 'auto';
20
+ if (_ready && _backend === backend) return _ready;
21
+ _backend = backend;
22
+ return (_ready = (async () => {
23
+ const { instance } = await instantiateWithBackend({ getSimdBytes, getBaseBytes, imports, backend });
17
24
  setInstance(instance);
18
25
  })());
19
26
  }
package/dist/node.d.ts CHANGED
@@ -1,2 +1,5 @@
1
- export function init(imports?: WebAssembly.Imports): Promise<void>;
1
+ export interface InitOptions {
2
+ backend?: 'auto' | 'simd' | 'base';
3
+ }
4
+ export function init(imports?: WebAssembly.Imports, opts?: InitOptions): Promise<void>;
2
5
  export * from "./custom.js";
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { setInstance, registerInit } from "./core.js";
2
- import { instantiateWithFallback } from "./util.js";
2
+ import { instantiateWithBackend } from "./util.js";
3
3
 
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
@@ -7,20 +7,23 @@ import { fileURLToPath } from "node:url";
7
7
  const simdPath = fileURLToPath(new URL("./wasm/gzip.simd.wasm", import.meta.url));
8
8
  const basePath = fileURLToPath(new URL("./wasm/gzip.base.wasm", import.meta.url));
9
9
 
10
- async function getWasmBytes() {
11
- const [simdBytes, baseBytes] = await Promise.all([
12
- readFile(simdPath).catch(() => null),
13
- readFile(basePath).catch(() => null)
14
- ]);
15
- return { simdBytes, baseBytes };
10
+ async function getSimdBytes() {
11
+ return readFile(simdPath);
12
+ }
13
+
14
+ async function getBaseBytes() {
15
+ return readFile(basePath);
16
16
  }
17
17
 
18
18
 
19
19
  let _ready = null;
20
- export function init(imports = {}) {
21
- return (_ready ??= (async () => {
22
- const { simdBytes, baseBytes } = await getWasmBytes();
23
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
20
+ let _backend = null;
21
+ export function init(imports = {}, opts = {}) {
22
+ const backend = opts.backend || 'auto';
23
+ if (_ready && _backend === backend) return _ready;
24
+ _backend = backend;
25
+ return (_ready = (async () => {
26
+ const { instance } = await instantiateWithBackend({ getSimdBytes, getBaseBytes, imports, backend });
24
27
  setInstance(instance);
25
28
  })());
26
29
  }
package/dist/util.js CHANGED
@@ -12,3 +12,33 @@ export async function instantiateWithFallback(
12
12
  return { instance, backend: 'wasm' }
13
13
  }
14
14
  }
15
+
16
+ export async function instantiateWithBackend({
17
+ getSimdBytes,
18
+ getBaseBytes,
19
+ imports,
20
+ backend = 'auto',
21
+ }) {
22
+ if (backend === 'base') {
23
+ const baseBytes = await getBaseBytes()
24
+ const { instance } = await WebAssembly.instantiate(baseBytes, imports)
25
+ return { instance, backend: 'wasm' }
26
+ }
27
+
28
+ if (backend === 'simd') {
29
+ const simdBytes = await getSimdBytes()
30
+ const { instance } = await WebAssembly.instantiate(simdBytes, imports)
31
+ return { instance, backend: 'wasm-simd' }
32
+ }
33
+
34
+ // auto: try simd first, then fallback to baseline
35
+ try {
36
+ const simdBytes = await getSimdBytes()
37
+ const { instance } = await WebAssembly.instantiate(simdBytes, imports)
38
+ return { instance, backend: 'wasm-simd' }
39
+ } catch {
40
+ const baseBytes = await getBaseBytes()
41
+ const { instance } = await WebAssembly.instantiate(baseBytes, imports)
42
+ return { instance, backend: 'wasm' }
43
+ }
44
+ }
Binary file
Binary file