@addmaple/brotli 0.1.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/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @addmaple/brotli
2
+
3
+ Fast Brotli compression in the browser and Node.js using Rust + WASM with SIMD optimizations.
4
+
5
+ **1.3x faster** than the `brotli` npm package (JS port of native C) at compression level 9.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @addmaple/brotli
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```javascript
16
+ import { init, compress } from '@addmaple/brotli';
17
+
18
+ await init();
19
+
20
+ const input = new TextEncoder().encode('hello world');
21
+ const compressed = await compress(input, { level: 9 });
22
+ ```
23
+
24
+ ### Inline (Zero-latency)
25
+
26
+ WASM bytes embedded directly in JS — no separate file fetching:
27
+
28
+ ```javascript
29
+ import { init, compress } from '@addmaple/brotli/inline';
30
+
31
+ await init();
32
+ const compressed = await compress(input);
33
+ ```
34
+
35
+ ## API
36
+
37
+ ### `init()`
38
+ Initialize the WASM module. Automatically detects SIMD support.
39
+
40
+ ### `compress(input, options?)`
41
+ - `input`: `Uint8Array`
42
+ - `options.level`: 1-11 (default: 9)
43
+ - Returns: `Promise<Uint8Array>`
44
+
45
+ ## Sponsor
46
+
47
+ Development of this module was sponsored by [addmaple.com](https://addmaple.com) — a modern data analysis platform.
48
+
49
+ ## License
50
+
51
+ MIT
52
+
@@ -0,0 +1,2 @@
1
+ export function init(imports?: WebAssembly.Imports): Promise<void>;
2
+ export * from "./custom.js";
@@ -0,0 +1,22 @@
1
+ import { setInstance, registerInit } from "./core.js";
2
+ import { instantiateWithFallback } from "./util.js";
3
+
4
+ import { wasmBytes as simdBytes } from "./wasm-inline/brotli.simd.wasm.js";
5
+ import { wasmBytes as baseBytes } from "./wasm-inline/brotli.base.wasm.js";
6
+
7
+ async function getWasmBytes() {
8
+ return { simdBytes, baseBytes };
9
+ }
10
+
11
+
12
+ 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
+ setInstance(instance);
18
+ })());
19
+ }
20
+
21
+ registerInit(init);
22
+ export * from "./custom.js";
@@ -0,0 +1,2 @@
1
+ export function init(imports?: WebAssembly.Imports): Promise<void>;
2
+ export * from "./custom.js";
@@ -0,0 +1,24 @@
1
+ import { setInstance, registerInit } from "./core.js";
2
+ import { instantiateWithFallback } from "./util.js";
3
+
4
+ const simdUrl = new URL("./wasm/brotli.simd.wasm", import.meta.url);
5
+ const baseUrl = new URL("./wasm/brotli.base.wasm", import.meta.url);
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 };
11
+ }
12
+
13
+
14
+ 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
+ setInstance(instance);
20
+ })());
21
+ }
22
+
23
+ registerInit(init);
24
+ export * from "./custom.js";
package/dist/core.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export type WasmInput = Uint8Array | ArrayBufferView | ArrayBuffer;
2
+
3
+ export function setInstance(instance: WebAssembly.Instance): void;
4
+ export function wasmExports(): WebAssembly.Exports;
5
+ export function memoryU8(): Uint8Array;
6
+ export function alloc(len: number): number;
7
+ export function free(ptr: number, len: number): void;
8
+
9
+ export function compress_level_1(input: WasmInput): Promise<Uint8Array>;
10
+ export function compress_level_4(input: WasmInput): Promise<Uint8Array>;
11
+ export function compress_level_6(input: WasmInput): Promise<Uint8Array>;
12
+ export function compress_level_9(input: WasmInput): Promise<Uint8Array>;
package/dist/core.js ADDED
@@ -0,0 +1,140 @@
1
+ let _inst = null;
2
+ let _memU8 = null;
3
+ let _initFn = null;
4
+
5
+ function refreshViews() {
6
+ _memU8 = new Uint8Array(_inst.exports.memory.buffer);
7
+ }
8
+
9
+ export function setInstance(instance) {
10
+ _inst = instance;
11
+ refreshViews();
12
+ }
13
+
14
+ export function wasmExports() {
15
+ return _inst.exports;
16
+ }
17
+
18
+ let _ready = null;
19
+ export function registerInit(fn) { _initFn = fn; }
20
+
21
+ async function ensureReady() {
22
+ if (_ready) return _ready;
23
+ if (!_initFn) throw new Error("init not registered");
24
+ _ready = _initFn();
25
+ return _ready;
26
+ }
27
+
28
+ export function memoryU8() {
29
+ if (_memU8 && _memU8.buffer !== _inst.exports.memory.buffer) refreshViews();
30
+ return _memU8;
31
+ }
32
+
33
+ export function alloc(len) {
34
+ return _inst.exports.alloc_bytes(len) >>> 0;
35
+ }
36
+
37
+ export function free(ptr, len) {
38
+ _inst.exports.free_bytes(ptr >>> 0, len >>> 0);
39
+ }
40
+
41
+ function toBytes(input) {
42
+ if (input instanceof Uint8Array) return input;
43
+ if (ArrayBuffer.isView(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
44
+ if (input instanceof ArrayBuffer) return new Uint8Array(input);
45
+ throw new TypeError("Expected a TypedArray or ArrayBuffer");
46
+ }
47
+
48
+ function callWasm(abi, input, outLen, reuse) {
49
+ if (!_inst) throw new Error("WASM instance not initialized");
50
+ const view = toBytes(input);
51
+ const len = view.byteLength;
52
+
53
+ let inPtr, outPtr;
54
+ if (reuse) {
55
+ if (reuse.in.len < len) {
56
+ if (reuse.in.ptr) free(reuse.in.ptr, reuse.in.len);
57
+ reuse.in.ptr = alloc(len);
58
+ reuse.in.len = len;
59
+ }
60
+ if (reuse.out.len < outLen) {
61
+ if (reuse.out.ptr) free(reuse.out.ptr, reuse.out.len);
62
+ reuse.out.ptr = alloc(outLen);
63
+ reuse.out.len = outLen;
64
+ }
65
+ inPtr = reuse.in.ptr;
66
+ outPtr = reuse.out.ptr;
67
+ } else {
68
+ inPtr = alloc(len);
69
+ outPtr = alloc(outLen);
70
+ }
71
+
72
+ memoryU8().set(view, inPtr);
73
+ const written = _inst.exports[abi](inPtr, len, outPtr, outLen);
74
+ if (written < 0) {
75
+ if (!reuse) { free(inPtr, len); free(outPtr, outLen); }
76
+ throw new Error(abi + " failed: " + written);
77
+ }
78
+
79
+ return { inPtr, outPtr, len, outLen, written };
80
+ }
81
+
82
+ async function compress_level_1(input) {
83
+ await ensureReady();
84
+ const view = toBytes(input);
85
+ const len = view.byteLength;
86
+ const outLen = len + 1024;
87
+ const { outPtr, written, inPtr } = callWasm("compress_brotli_level_1", view, outLen, null);
88
+
89
+ const result = memoryU8().slice(outPtr, outPtr + written);
90
+
91
+ free(inPtr, len);
92
+ free(outPtr, outLen);
93
+ return result;
94
+ }
95
+ export { compress_level_1 };
96
+
97
+ async function compress_level_4(input) {
98
+ await ensureReady();
99
+ const view = toBytes(input);
100
+ const len = view.byteLength;
101
+ const outLen = len + 1024;
102
+ const { outPtr, written, inPtr } = callWasm("compress_brotli_level_4", view, outLen, null);
103
+
104
+ const result = memoryU8().slice(outPtr, outPtr + written);
105
+
106
+ free(inPtr, len);
107
+ free(outPtr, outLen);
108
+ return result;
109
+ }
110
+ export { compress_level_4 };
111
+
112
+ async function compress_level_6(input) {
113
+ await ensureReady();
114
+ const view = toBytes(input);
115
+ const len = view.byteLength;
116
+ const outLen = len + 1024;
117
+ const { outPtr, written, inPtr } = callWasm("compress_brotli_level_6", view, outLen, null);
118
+
119
+ const result = memoryU8().slice(outPtr, outPtr + written);
120
+
121
+ free(inPtr, len);
122
+ free(outPtr, outLen);
123
+ return result;
124
+ }
125
+ export { compress_level_6 };
126
+
127
+ async function compress_level_9(input) {
128
+ await ensureReady();
129
+ const view = toBytes(input);
130
+ const len = view.byteLength;
131
+ const outLen = len + 1024;
132
+ const { outPtr, written, inPtr } = callWasm("compress_brotli_level_9", view, outLen, null);
133
+
134
+ const result = memoryU8().slice(outPtr, outPtr + written);
135
+
136
+ free(inPtr, len);
137
+ free(outPtr, outLen);
138
+ return result;
139
+ }
140
+ export { compress_level_9 };
package/dist/custom.js ADDED
@@ -0,0 +1,20 @@
1
+ import { compress_level_1, compress_level_4, compress_level_6, compress_level_9, wasmExports } from './core.js';
2
+
3
+ export async function compress(input, options = {}) {
4
+ const level = options.level ?? 9;
5
+
6
+ try {
7
+ if (level <= 1) return compress_level_1(input);
8
+ if (level <= 4) return compress_level_4(input);
9
+ if (level <= 6) return compress_level_6(input);
10
+ return compress_level_9(input);
11
+ } catch (error) {
12
+ throw new Error(`Compression failed: ${error.message}`);
13
+ }
14
+ }
15
+
16
+ export function getLoadedVariant() {
17
+ return 'lite';
18
+ }
19
+
20
+ export { wasmExports };
@@ -0,0 +1,2 @@
1
+ export function init(imports?: WebAssembly.Imports): Promise<void>;
2
+ export * from "./custom.js";
@@ -0,0 +1,22 @@
1
+ import { setInstance, registerInit } from "./core.js";
2
+ import { instantiateWithFallback } from "./util.js";
3
+
4
+ import { wasmBytes as simdBytes } from "./wasm-inline/brotli.simd.wasm.js";
5
+ import { wasmBytes as baseBytes } from "./wasm-inline/brotli.base.wasm.js";
6
+
7
+ async function getWasmBytes() {
8
+ return { simdBytes, baseBytes };
9
+ }
10
+
11
+
12
+ 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
+ setInstance(instance);
18
+ })());
19
+ }
20
+
21
+ registerInit(init);
22
+ export * from "./custom.js";
package/dist/node.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export function init(imports?: WebAssembly.Imports): Promise<void>;
2
+ export * from "./custom.js";
package/dist/node.js ADDED
@@ -0,0 +1,29 @@
1
+ import { setInstance, registerInit } from "./core.js";
2
+ import { instantiateWithFallback } from "./util.js";
3
+
4
+ import { readFile } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const simdPath = fileURLToPath(new URL("./wasm/brotli.simd.wasm", import.meta.url));
8
+ const basePath = fileURLToPath(new URL("./wasm/brotli.base.wasm", import.meta.url));
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 };
16
+ }
17
+
18
+
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);
24
+ setInstance(instance);
25
+ })());
26
+ }
27
+
28
+ registerInit(init);
29
+ export * from "./custom.js";
package/dist/util.js ADDED
@@ -0,0 +1,14 @@
1
+ export async function instantiateWithFallback(
2
+ trySimdBytes,
3
+ baseBytes,
4
+ imports
5
+ ) {
6
+ try {
7
+ const { instance } = await WebAssembly.instantiate(trySimdBytes, imports)
8
+ return { instance, backend: 'wasm-simd' }
9
+ } catch {
10
+ // If SIMD fails (not supported), try baseline
11
+ const { instance } = await WebAssembly.instantiate(baseBytes, imports)
12
+ return { instance, backend: 'wasm' }
13
+ }
14
+ }
Binary file
Binary file