Skip to content

Implement zstd compression and decompression - #12

Merged
derodero24 merged 4 commits into
developfrom
feat/issue-4-zstd-compress
Mar 20, 2026
Merged

derodero24 merged 4 commits into
developfrom
feat/issue-4-zstd-compress

Conversation

@derodero24

@derodero24 derodero24 commented Mar 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Add zstd compression and decompression as the first core feature of zflate. This uses the zstd C library (v1.5.7) via the zstd Rust crate for maximum compatibility and performance across all compression levels.

Closes #4

API

import { zstdCompress, zstdDecompress } from 'zflate';

// Compress with default level (3)
const compressed = zstdCompress(data);

// Compress with specific level (1=fastest, 22=best)
const fast = zstdCompress(data, 1);

// Decompress
const original = zstdDecompress(compressed);

// Decompress with explicit capacity (for data > 256 MB)
const large = zstdDecompressWithCapacity(compressed, 500_000_000);

What's included

Rust core (crates/core/src/zstd.rs)

  • zstd_compress(data, level?) — one-shot compression, levels 1-22
  • zstd_decompress(data) — one-shot decompression with auto frame size detection
  • zstd_decompress_with_capacity(data, capacity) — explicit capacity for large data
  • 4 Rust unit tests (round-trip, empty, large, compression levels)

JS tests (__test__/zstd.spec.ts)

  • 11 tests: round-trip at various sizes (0B, 1B, 1KB, 1MB), compression levels, error handling, ESM

Benchmarks (__test__/index.bench.ts)

  • Compress/decompress at 150B, 10KB, 1MB

Other

  • ESM exports updated (index.mjs)
  • napi-rs generated files regenerated
  • Changeset for minor version bump

Checklist

  • Lint passes (pnpm run check)
  • TypeScript type-check passes (pnpm run typecheck)
  • JS tests pass (pnpm test) — 11 tests
  • Rust tests pass (cargo test) — 4 tests
  • Clippy passes (cargo clippy)
  • Build succeeds (pnpm run build)
  • Changeset included

Summary by CodeRabbit

リリースノート

  • 新機能

    • Zstandard(zstd)による圧縮・展開機能を追加。圧縮レベル1–22(デフォルト3)と展開時の容量指定オプションを提供
    • パッケージのマイナーリリースへバージョン更新
  • 公開API

    • 圧縮/展開関連の新しいエクスポートを追加(トップレベルで利用可能に)
  • テスト

    • ユニット/ESMラウンドトリップテストと複数サイズのベンチマークを追加
  • CI

    • wasm向けビルドにWASI SDKセットアップを導入するワークフロー改善

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels
- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions
- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump
@coderabbitai

coderabbitai Bot commented Mar 20, 2026 •

Copy link
Copy Markdown

Walkthrough

zstd 圧縮・解凍をネイティブ実装として追加。Rust コード、N-API エクスポート、TypeScript 宣言、JS バインディング、テスト、ベンチマーク、Changeset および Cargo 依存が含まれる。

Changes

Cohort / File(s) Summary
Changeset
\.changeset/zstd-compress.md
zflate のマイナーリリースを記録。zstdCompress() と zstdDecompress() 導入、圧縮レベル 1–22(デフォルト 3)を文書化。
ワークスペース依存
Cargo.toml
ワークスペース依存として zstd = { version = "0.13", default-features = false } を追加。
クレート依存
crates/core/Cargo.toml
crates/core に workspace 由来の zstd 依存を追加。
Rust 実装とエクスポート
crates/core/src/lib.rs, crates/core/src/zstd.rs
新しい zstd モジュール追加。zstd_compress, zstd_decompress, zstd_decompress_with_capacity を N-API で公開。デフォルトレベル 3、最大解凍サイズ 256MB を実装。単体テストあり。
JS/TS バインディング
index.js, index.mjs, index.d.ts
トップレベルで zstdCompress/zstdDecompress/zstdDecompressWithCapacity をエクスポートし、型定義を追加。
テスト
__test__/zstd.spec.ts, __test__/esm-import.mjs
多サイズのラウンドトリップ、圧縮レベル比較、デフォルトレベル確認、無効データエラーハンドリング、容量指定デコードなどのテストを追加。ESM インポート検証を更新。
ベンチマーク
__test__/index.bench.ts
150B、10KB、1MB 入力での圧縮・解凍ベンチマークを追加。固定バッファと事前圧縮データを使用。
CI / Release ワークフロー
.github/workflows/ci.yml, .github/workflows/release.yml
wasm32-wasip1-threads 向けに WASI SDK をセットアップする条件付きステップと、sysroot を用いたビルドフローを追加・分岐。

Sequence Diagram

sequenceDiagram
    participant JS as JavaScriptコード
    participant NAPI as Nativeバインディング
    participant Rust as Rust実装\ \(zstd.rs\)
    participant ZSTDLib as Zstdライブラリ

    rect rgba(100,200,150,0.5)
    Note over JS,NAPI: 圧縮フロー
    JS->>NAPI: zstdCompress(data, level)
    NAPI->>Rust: zstd_compress(data, level)
    Rust->>ZSTDLib: zstd::bulk::compress()
    ZSTDLib-->>Rust: compressed bytes
    Rust-->>NAPI: Buffer(compressed)
    NAPI-->>JS: Buffer(compressed)
    end

    rect rgba(150,150,200,0.5)
    Note over JS,ZSTDLib: 解凍フロー
    JS->>NAPI: zstdDecompress(data)
    NAPI->>Rust: zstd_decompress(data)
    Rust->>Rust: get_frame_content_size()
    Rust->>ZSTDLib: zstd::bulk::decompress()
    ZSTDLib-->>Rust: decompressed bytes
    Rust-->>NAPI: Buffer(decompressed)
    NAPI-->>JS: Buffer(decompressed)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 ぴょん、データが小さくなるよ
しゅっ、速く戻るよ、丸ごと一周
レベルいくつでも仲良く解凍
テストもベンチもぴょんぴょん合格
新しい zstd、草原を跳ねる 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR title clearly summarizes the primary change: adding zstd compression and decompression support.
Description check ✅ Passed PR description comprehensively covers all required template sections with detailed implementation details, test results, and passing checklist items.
Linked Issues check ✅ Passed PR implements core requirements from issue #4: zstd Rust functions, JS/TS APIs, round-trip tests, compression levels 1-22, and WASM build support. Dictionary support is deferred.
Out of Scope Changes check ✅ Passed All changes are scoped to zstd implementation (Rust core, JS bindings, tests, benchmarks, CI/CD for WASM support). No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-4-zstd-compress
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed

codspeed Bot commented Mar 20, 2026 •

Copy link
Copy Markdown

Merging this PR will create unknown performance changes

🆕 6 new benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 1MB N/A 613 µs N/A
🆕 10KB N/A 282.6 µs N/A
🆕 150B N/A 47.8 µs N/A
🆕 150B N/A 71.6 µs N/A
🆕 10KB N/A 54.8 µs N/A
🆕 1MB N/A 2 ms N/A

Comparing feat/issue-4-zstd-compress (6edde26) with develop (990a34e)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports. ↩

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
.changeset/zstd-compress.md (1)

5-5: zstdDecompressWithCapacity() の記載漏れ

チェンジセットの説明に zstdDecompressWithCapacity() 関数が含まれていません。公開APIの一部として追加することを検討してください。

📝 修正案
-Add zstd compression and decompression support via `zstdCompress()` and `zstdDecompress()` functions. Supports compression levels 1-22 (default: 3).
+Add zstd compression and decompression support via `zstdCompress()`, `zstdDecompress()`, and `zstdDecompressWithCapacity()` functions. Supports compression levels 1-22 (default: 3). Use `zstdDecompressWithCapacity()` for data larger than 256 MB.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/zstd-compress.md at line 5, The changeset description currently
lists zstdCompress() and zstdDecompress() but omits
zstdDecompressWithCapacity(); update the .changeset text to mention
zstdDecompressWithCapacity() as part of the public API, noting it provides
decompression with a user-provided output capacity parameter and behaves
consistently with the other functions (supports zstd levels 1–22, default level
3). Ensure the description references zstdCompress(), zstdDecompress(), and
zstdDecompressWithCapacity() by name so consumers know all exposed APIs.
__test__/index.bench.ts (1)

4-8: テストデータの初期化について

MEDIUMとLARGEのバッファはi % 256のパターンで初期化されています。これは適度に圧縮可能なデータを生成しますが、実際のユースケースによっては、ランダムデータや実際のファイル内容(JSON、テキストなど)でもベンチマークを取ることを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__test__/index.bench.ts` around lines 4 - 8, The benchmark currently
initializes MEDIUM and LARGE with a repeating i % 256 pattern (constants SMALL,
MEDIUM, LARGE in __test__/index.bench.ts), which may not reflect real workloads;
update the test to include additional dataset variants: (a) a random-data
variant using crypto.randomFillSync for MEDIUM_RANDOM and LARGE_RANDOM, and (b)
a real-file/text variant by reading sample files (e.g., JSON/text fixtures) into
MEDIUM_REAL and LARGE_REAL via fs.readFileSync, and use these alongside the
existing patterned buffers in your benchmark runs so you can compare compression
behavior across patterned, random, and realistic inputs.
__test__/zstd.spec.ts (2)

1-76: テストカバレッジは良好ですが、境界値テストの追加を検討してください。

ラウンドトリップテスト、圧縮レベル、エラーハンドリングの基本的なカバレッジは十分です。オプションとして、以下のエッジケースのテスト追加を検討できます:

  • 無効な圧縮レベル(0、23、負の値)に対するエラーハンドリング
  • レベル22(最高圧縮)のテスト
💡 境界値テストの例
it('should handle compression level boundaries', () => {
  const input = Buffer.from('test data '.repeat(50));
  
  // Level 22 (maximum) should work
  const maxLevel = zstdCompress(input, 22);
  expect(zstdDecompress(maxLevel)).toEqual(input);
  
  // Invalid levels should throw
  expect(() => zstdCompress(input, 0)).toThrow();
  expect(() => zstdCompress(input, 23)).toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__test__/zstd.spec.ts` around lines 1 - 76, Add boundary-value tests for
zstdCompress/zstdDecompress: create a new it block that calls
zstdCompress(input, 22) and asserts zstdDecompress returns the original buffer,
and also asserts zstdCompress(input, 0) and zstdCompress(input, 23) (and a
negative level) throw. Locate tests near existing zstdCompress/zstdDecompress
usages in __test__/zstd.spec.ts (functions: zstdCompress, zstdDecompress) and
follow the same input setup and expect(...) patterns used in the file.

78-85: zstdDecompressWithCapacityのテストカバレッジ拡充を検討してください。

現在のテストは基本的な動作を検証していますが、以下のケースも考慮できます:

  • 不十分なキャパシティでのエラーハンドリング
  • 必要サイズより大きいキャパシティでの動作
💡 追加テストの例
it('should work with capacity larger than needed', () => {
  const input = Buffer.from('Hello with capacity!');
  const compressed = zstdCompress(input);
  const decompressed = zstdDecompressWithCapacity(compressed, input.length * 2);
  expect(decompressed).toEqual(input);
});

it('should throw when capacity is insufficient', () => {
  const input = Buffer.alloc(1000, 'x');
  const compressed = zstdCompress(input);
  expect(() => zstdDecompressWithCapacity(compressed, 10)).toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__test__/zstd.spec.ts` around lines 78 - 85, Add two unit tests for
zstdDecompressWithCapacity in __test__/zstd.spec.ts: one that passes a capacity
larger than the original (use zstdCompress to create compressed data and call
zstdDecompressWithCapacity(compressed, input.length * 2) and assert equality
with the original) and another that asserts an error is thrown when capacity is
insufficient (create a large input, compress with zstdCompress, then expect
zstdDecompressWithCapacity(compressed, smallCapacity) to throw). Ensure tests
reference zstdDecompressWithCapacity and zstdCompress and use appropriate
expect(...).toEqual(...) and expect(...).toThrow() assertions.
crates/core/src/zstd.rs (1)

17-24: 圧縮レベルの検証提案は不完全です。

ドキュメント検索結果によると、zstdライブラリは正の値(1~22)だけでなく、負のレベル(-131072までの値)もサポートしています。負のレベルは速度を優先する場合に使用されます。提案されている検証(1..=22)は、この有効な負のレベルを拒否してしまい、実装を制限することになります。

現在のコードは検証なしでzstd::bulk::compressに直接レベルを渡しており、これはzstd側で有効性をチェックするため合理的です。ただし、ドキュメント(17行目のコメント)は負のレベルをサポートしていることを明示すべきです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/core/src/zstd.rs` around lines 17 - 24, Update the comment above
zstd_compress to explicitly state that zstd accepts both positive (1..=22) and
negative levels (down to -131072) and that negative values favor speed,
clarifying that this function currently forwards the level directly to
zstd::bulk::compress (so no additional validation is performed) and that
DEFAULT_LEVEL remains the fallback when level is None; reference the
zstd_compress function, DEFAULT_LEVEL, and the call to zstd::bulk::compress in
the comment to make the behavior and supported range clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/core/src/zstd.rs`:
- Around line 59-71: Validate the capacity f64 in zstd_decompress_with_capacity
before casting: check that capacity.is_finite() and capacity >= 0.0 and capacity
<= (usize::MAX as f64) (or a project-defined max) and reject otherwise with an
appropriate Error (e.g., Status::InvalidArgument) so you don't cast
negative/NaN/Infinity into a huge usize; only after these checks convert to
usize (cap = capacity as usize) and proceed to call zstd::bulk::decompress.

---

Nitpick comments:
In `@__test__/index.bench.ts`:
- Around line 4-8: The benchmark currently initializes MEDIUM and LARGE with a
repeating i % 256 pattern (constants SMALL, MEDIUM, LARGE in
__test__/index.bench.ts), which may not reflect real workloads; update the test
to include additional dataset variants: (a) a random-data variant using
crypto.randomFillSync for MEDIUM_RANDOM and LARGE_RANDOM, and (b) a
real-file/text variant by reading sample files (e.g., JSON/text fixtures) into
MEDIUM_REAL and LARGE_REAL via fs.readFileSync, and use these alongside the
existing patterned buffers in your benchmark runs so you can compare compression
behavior across patterned, random, and realistic inputs.

In `@__test__/zstd.spec.ts`:
- Around line 1-76: Add boundary-value tests for zstdCompress/zstdDecompress:
create a new it block that calls zstdCompress(input, 22) and asserts
zstdDecompress returns the original buffer, and also asserts zstdCompress(input,
0) and zstdCompress(input, 23) (and a negative level) throw. Locate tests near
existing zstdCompress/zstdDecompress usages in __test__/zstd.spec.ts (functions:
zstdCompress, zstdDecompress) and follow the same input setup and expect(...)
patterns used in the file.
- Around line 78-85: Add two unit tests for zstdDecompressWithCapacity in
__test__/zstd.spec.ts: one that passes a capacity larger than the original (use
zstdCompress to create compressed data and call
zstdDecompressWithCapacity(compressed, input.length * 2) and assert equality
with the original) and another that asserts an error is thrown when capacity is
insufficient (create a large input, compress with zstdCompress, then expect
zstdDecompressWithCapacity(compressed, smallCapacity) to throw). Ensure tests
reference zstdDecompressWithCapacity and zstdCompress and use appropriate
expect(...).toEqual(...) and expect(...).toThrow() assertions.

In @.changeset/zstd-compress.md:
- Line 5: The changeset description currently lists zstdCompress() and
zstdDecompress() but omits zstdDecompressWithCapacity(); update the .changeset
text to mention zstdDecompressWithCapacity() as part of the public API, noting
it provides decompression with a user-provided output capacity parameter and
behaves consistently with the other functions (supports zstd levels 1–22,
default level 3). Ensure the description references zstdCompress(),
zstdDecompress(), and zstdDecompressWithCapacity() by name so consumers know all
exposed APIs.

In `@crates/core/src/zstd.rs`:
- Around line 17-24: Update the comment above zstd_compress to explicitly state
that zstd accepts both positive (1..=22) and negative levels (down to -131072)
and that negative values favor speed, clarifying that this function currently
forwards the level directly to zstd::bulk::compress (so no additional validation
is performed) and that DEFAULT_LEVEL remains the fallback when level is None;
reference the zstd_compress function, DEFAULT_LEVEL, and the call to
zstd::bulk::compress in the comment to make the behavior and supported range
clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dcda9aed-de9a-44c5-9057-e3c59b218e80

📥 Commits

Reviewing files that changed from the base of the PR and between 990a34e and 690b2aa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .changeset/zstd-compress.md
  • Cargo.toml
  • __test__/esm-import.mjs
  • __test__/index.bench.ts
  • __test__/zstd.spec.ts
  • crates/core/Cargo.toml
  • crates/core/src/lib.rs
  • crates/core/src/zstd.rs
  • index.d.ts
  • index.js
  • index.mjs

Comment thread crates/core/src/zstd.rs
Comment on lines +59 to +71
pub fn zstd_decompress_with_capacity(data: Buffer, capacity: f64) -> Result<Buffer> {
let input = data.as_ref();
let cap = capacity as usize;

zstd::bulk::decompress(input, cap)
.map(|v| v.into())
.map_err(|e| {
Error::new(
Status::GenericFailure,
format!("zstd decompress failed: {e}"),
)
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

capacityパラメータの検証を追加してください。

f64からusizeへの変換は、負の値やNaN/Infinityで予期しない動作を引き起こす可能性があります。負のf64は非常に大きなusizeにラップし、メモリ割り当てエラーやDoSの原因となる可能性があります。

🛡️ 検証の追加案
 #[napi]
 pub fn zstd_decompress_with_capacity(data: Buffer, capacity: f64) -> Result<Buffer> {
+    if !capacity.is_finite() || capacity < 0.0 {
+        return Err(Error::new(
+            Status::InvalidArg,
+            "capacity must be a positive finite number",
+        ));
+    }
     let input = data.as_ref();
     let cap = capacity as usize;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/core/src/zstd.rs` around lines 59 - 71, Validate the capacity f64 in
zstd_decompress_with_capacity before casting: check that capacity.is_finite()
and capacity >= 0.0 and capacity <= (usize::MAX as f64) (or a project-defined
max) and reject otherwise with an appropriate Error (e.g.,
Status::InvalidArgument) so you don't cast negative/NaN/Infinity into a huge
usize; only after these checks convert to usize (cap = capacity as usize) and
proceed to call zstd::bulk::decompress.

@derodero24
derodero24 force-pushed the feat/issue-4-zstd-compress branch from 5c1e60e to e81426b Compare March 20, 2026 05:20
The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.
@derodero24
derodero24 force-pushed the feat/issue-4-zstd-compress branch from e81426b to 6edde26 Compare March 20, 2026 05:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 166-170: The workflow extracts the WASI SDK to /opt which can be
unwritable on GitHub runners; change extraction to use $RUNNER_TEMP by
creating/using a temp dir and extracting the tarball there, then set
WASI_SDK_PATH and WASI_SYSROOT to point into that temp location (update
references to WASI_SDK_VERSION, WASI_SDK_DIR, WASI_SDK_PATH, and WASI_SYSROOT).
Ensure the directory exists before extraction and retain the same archive URL
and tar extraction flags, only replacing /opt with the chosen $RUNNER_TEMP path
so CI no longer fails due to permission issues.
- Line 168: 現在の curl | tar 解凍ステップは配布物の完全性検証を行っていないため、WASI_SDK のリリース SHA256
ダイジェストを取得して検証するように変更してください: 使用している変数 WASI_SDK_VERSION と WASI_SDK_DIR
をそのまま使い、まず該当リリースの .tar.gz とともに提供される SHA256 値(または GitHub Releases
の署名付きハッシュ)をダウンロードまたは取得し、ダウンロードしたアーカイブの sha256 を確認(sha256sum -c あるいは echo
"<expected>  ${WASI_SDK_DIR}.tar.gz" | sha256sum -c
-)して不一致ならジョブを失敗させる処理を追加してから安全に tar xz -C /opt で展開するようにしてください。

In @.github/workflows/release.yml:
- Around line 155-159: Replace the hard-coded /opt extraction with a
runner-writable directory: use WASI_SDK_DIR and WASI_SDK_VERSION but extract
into $RUNNER_TEMP (create the target dir with mkdir -p) and then set
WASI_SDK_PATH and WASI_SYSROOT to point under $RUNNER_TEMP/${WASI_SDK_DIR};
update the curl | tar command to use tar -C "$RUNNER_TEMP" and ensure the
environment echoes reference "$RUNNER_TEMP/${WASI_SDK_DIR}" so the workflow no
longer depends on writing to /opt.
- Line 157: The workflow is downloading the wasi-sdk tarball via the curl | tar
pipeline (see the curl command using WASI_SDK_VERSION and WASI_SDK_DIR) without
integrity checks; change it to download the archive to a file, verify its SHA256
against a pinned hash (either stored in-repo or passed as a workflow
secret/variable) using sha256sum -c (or equivalent) and only extract with tar
after verification succeeds; reference the variables WASI_SDK_VERSION and
WASI_SDK_DIR when locating the downloaded file and the checksum entry so the
verification step fails the job on mismatch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5be5560-e191-45d6-9181-21c5bc79def0

📥 Commits

Reviewing files that changed from the base of the PR and between 690b2aa and 6edde26.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml

Comment thread .github/workflows/ci.yml
Comment on lines +166 to +170
WASI_SDK_VERSION="25"
WASI_SDK_DIR="wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux"
curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" | tar xz -C /opt
echo "WASI_SDK_PATH=/opt/${WASI_SDK_DIR}" >> $GITHUB_ENV
echo "WASI_SYSROOT=/opt/${WASI_SDK_DIR}/share/wasi-sysroot" >> $GITHUB_ENV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

/opt 展開は権限不足で失敗する可能性があります

GitHub Actions ランナーでは /opt が書き込み不可のケースがあり、WASM ターゲットだけ CI が落ちるリスクがあります。$RUNNER_TEMP 配下へ展開してください。

修正例
-          curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" | tar xz -C /opt
-          echo "WASI_SDK_PATH=/opt/${WASI_SDK_DIR}" >> $GITHUB_ENV
-          echo "WASI_SYSROOT=/opt/${WASI_SDK_DIR}/share/wasi-sysroot" >> $GITHUB_ENV
-          echo "CC_wasm32_wasip1_threads=/opt/${WASI_SDK_DIR}/bin/clang" >> $GITHUB_ENV
-          echo "AR_wasm32_wasip1_threads=/opt/${WASI_SDK_DIR}/bin/llvm-ar" >> $GITHUB_ENV
+          WASI_SDK_ROOT="${RUNNER_TEMP}/wasi-sdk"
+          mkdir -p "${WASI_SDK_ROOT}"
+          curl -fsSL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" -o "${RUNNER_TEMP}/${WASI_SDK_DIR}.tar.gz"
+          tar xzf "${RUNNER_TEMP}/${WASI_SDK_DIR}.tar.gz" -C "${WASI_SDK_ROOT}"
+          echo "WASI_SDK_PATH=${WASI_SDK_ROOT}/${WASI_SDK_DIR}" >> $GITHUB_ENV
+          echo "WASI_SYSROOT=${WASI_SDK_ROOT}/${WASI_SDK_DIR}/share/wasi-sysroot" >> $GITHUB_ENV
+          echo "CC_wasm32_wasip1_threads=${WASI_SDK_ROOT}/${WASI_SDK_DIR}/bin/clang" >> $GITHUB_ENV
+          echo "AR_wasm32_wasip1_threads=${WASI_SDK_ROOT}/${WASI_SDK_DIR}/bin/llvm-ar" >> $GITHUB_ENV
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 166 - 170, The workflow extracts the
WASI SDK to /opt which can be unwritable on GitHub runners; change extraction to
use $RUNNER_TEMP by creating/using a temp dir and extracting the tarball there,
then set WASI_SDK_PATH and WASI_SYSROOT to point into that temp location (update
references to WASI_SDK_VERSION, WASI_SDK_DIR, WASI_SDK_PATH, and WASI_SYSROOT).
Ensure the directory exists before extraction and retain the same archive URL
and tar extraction flags, only replacing /opt with the chosen $RUNNER_TEMP path
so CI no longer fails due to permission issues.

Comment thread .github/workflows/ci.yml
run: |
WASI_SDK_VERSION="25"
WASI_SDK_DIR="wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux"
curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" | tar xz -C /opt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the CI workflow file around line 168
cat -n .github/workflows/ci.yml | sed -n '160,180p'

Repository: derodero24/zflate

Length of output: 1298


🏁 Script executed:

# Check if there's a WASI_SDK_VERSION variable defined
grep -n "WASI_SDK_VERSION" .github/workflows/ci.yml

Repository: derodero24/zflate

Length of output: 321


🏁 Script executed:

# Look for any SHA256 or checksum verification in the file
grep -n -i "sha256\|checksum\|integrity\|verify" .github/workflows/ci.yml

Repository: derodero24/zflate

Length of output: 123


🏁 Script executed:

# Let's check if official checksums are published for WASI SDK 25.0
# Check the GitHub releases page for WASI SDK
curl -sL "https://api.github.com/repos/WebAssembly/wasi-sdk/releases" | python3 << 'EOF'
import json
import sys

data = json.load(sys.stdin)
# Find release 25
for release in data:
    if release.get('tag_name') == 'wasi-sdk-25':
        print(f"Release: {release.get('tag_name')}")
        print(f"Body:\n{release.get('body', 'No body')}")
        print("\n--- Assets ---")
        for asset in release.get('assets', []):
            print(f"  {asset['name']}")
        break
EOF

Repository: derodero24/zflate

Length of output: 781


🏁 Script executed:

# Let's also check if there's a checksums file in the WASI SDK releases
curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/" 2>/dev/null | grep -i "sha256\|checksum\|asc\|sig" || echo "No checksum files found in directory listing"

Repository: derodero24/zflate

Length of output: 105


🏁 Script executed:

# Check if there are any security/integrity-related files in WASI SDK releases
# by looking at a known WASI SDK release page
curl -sL "https://api.github.com/repos/WebAssembly/wasi-sdk/releases/tags/wasi-sdk-25" | python3 << 'EOF'
import json
import sys

try:
    data = json.load(sys.stdin)
    print(f"Release Tag: {data.get('tag_name')}")
    print(f"Release Body:\n{data.get('body', 'N/A')}\n")
    print("Assets:")
    for asset in data.get('assets', []):
        print(f"  - {asset['name']}")
except json.JSONDecodeError:
    print("Could not parse JSON response")
EOF

Repository: derodero24/zflate

Length of output: 90


🌐 Web query:

WASI SDK official releases SHA256 checksums verification

💡 Result:

The official WASI SDK releases are hosted on GitHub at https://github.com/WebAssembly/wasi-sdk/releases. Pre-built binary tarballs (e.g., wasi-sdk-32.0-x86_64-linux.tar.gz) are available for download from each release page. No separate SHA256 checksums.txt file is provided in the releases. However, since June 2025, GitHub automatically computes and exposes SHA256 digests for all release assets. These can be viewed: - On the release page UI next to each asset (SHA256 checksum displayed). - Via GitHub CLI: gh release download --include-assets , then gh release verify-asset to verify using signed attestations. - Via REST API: GET /repos/WebAssembly/wasi-sdk/releases/assets/{asset_id} returns "digest" field (SHA256 hex prefixed with "sha256:"). To verify manually: 1. Download the asset tarball from the release page. 2. View the digest on the GitHub release asset page or API. 3. Compute local SHA256: sha256sum <downloaded-file.tar.gz>. 4. Compare the hex digest (remove "sha256:" prefix if present). Example for latest (wasi-sdk-32 as of 2026-03-20): - Navigate to https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-32 - Find asset e.g. "wasi-sdk-32.0-x86_64-linux.tar.gz", note its SHA256 next to download button. - After download: sha256sum wasi-sdk-32.0-x86_64-linux.tar.gz and match. Releases are GPG-signed commits (verified signature shown). For full integrity, check GitHub's signed attestations via gh release verify-asset. Latest release: wasi-sdk-32 (2026-03-11, 12 assets including Linux/x86_64, macOS, Windows, sysroot, etc.).

Citations:


🌐 Web query:

WebAssembly WASI SDK 25 release checksums security

💡 Result:

The WebAssembly WASI SDK version 25 (wasi-sdk-25) was released on December 12, 2024. Download official tarballs from https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-25. There are 11 assets (e.g., wasi-sdk-25.0-x86_64-linux.tar.gz for various platforms/architectures). Releases are signed with GitHub's GPG key (ID: B5690EEEBB952194), verifiable via GitHub's "verified" badge for integrity. No separate SHA256 checksum files are published by the project; verify downloads using GitHub's signature and commit verification. No security vulnerabilities, advisories, or compromises specific to WASI SDK 25 were found in official sources, GitHub security tabs, or related runtimes like Wasmtime. The release updates LLVM/Clang to 19, wasi-libc, and wasm-component-ld with bugfixes; prior issues (e.g., VERSION file in v24) were resolved in v25.

Citations:


🏁 Script executed:

# Check if there's any documentation or best practices about WASI SDK verification
# Let's also check what other CI/CD workflows do for similar tools
grep -r "wasi-sdk" . --include="*.yml" --include="*.yaml" 2>/dev/null | head -20

Repository: derodero24/zflate

Length of output: 612


🏁 Script executed:

# Let's check the WASI SDK GitHub repository directly for security/checksum info
# by examining if there are checksums published in release notes or elsewhere
curl -s "https://api.github.com/repos/WebAssembly/wasi-sdk/releases" --max-time 5 2>/dev/null | head -100 || echo "API call failed - checking alternative sources"

Repository: derodero24/zflate

Length of output: 5684


WASI SDK のダウンロードに完全性検証がありません

外部配布物をそのまま展開しているため、改ざん検知ができません。GitHub が公開する SHA256 ダイジェストを取得して検証してから解凍してください。

修正例
+          curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" -o "${RUNNER_TEMP}/${WASI_SDK_DIR}.tar.gz"
+          EXPECTED_SHA256=$(curl -sL "https://api.github.com/repos/WebAssembly/wasi-sdk/releases/latest" | jq -r '.assets[] | select(.name == "'${WASI_SDK_DIR}'.tar.gz") | .digest' | cut -d: -f2)
+          echo "${EXPECTED_SHA256}  ${RUNNER_TEMP}/${WASI_SDK_DIR}.tar.gz" | sha256sum -c -
+          tar xz -C /opt -f "${RUNNER_TEMP}/${WASI_SDK_DIR}.tar.gz"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml at line 168, 現在の curl | tar
解凍ステップは配布物の完全性検証を行っていないため、WASI_SDK のリリース SHA256 ダイジェストを取得して検証するように変更してください:
使用している変数 WASI_SDK_VERSION と WASI_SDK_DIR をそのまま使い、まず該当リリースの .tar.gz とともに提供される
SHA256 値(または GitHub Releases の署名付きハッシュ)をダウンロードまたは取得し、ダウンロードしたアーカイブの sha256
を確認(sha256sum -c あるいは echo "<expected>  ${WASI_SDK_DIR}.tar.gz" | sha256sum -c
-)して不一致ならジョブを失敗させる処理を追加してから安全に tar xz -C /opt で展開するようにしてください。

Comment on lines +155 to +159
WASI_SDK_VERSION="25"
WASI_SDK_DIR="wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux"
curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" | tar xz -C /opt
echo "WASI_SDK_PATH=/opt/${WASI_SDK_DIR}" >> $GITHUB_ENV
echo "WASI_SYSROOT=/opt/${WASI_SDK_DIR}/share/wasi-sysroot" >> $GITHUB_ENV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

リリースワークフローでも /opt 展開は失敗リスクがあります

CI と同様に、/opt への展開は権限依存です。公開パイプラインの安定性のため、$RUNNER_TEMP などユーザー書き込み可能な場所へ展開してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 155 - 159, Replace the hard-coded
/opt extraction with a runner-writable directory: use WASI_SDK_DIR and
WASI_SDK_VERSION but extract into $RUNNER_TEMP (create the target dir with mkdir
-p) and then set WASI_SDK_PATH and WASI_SYSROOT to point under
$RUNNER_TEMP/${WASI_SDK_DIR}; update the curl | tar command to use tar -C
"$RUNNER_TEMP" and ensure the environment echoes reference
"$RUNNER_TEMP/${WASI_SDK_DIR}" so the workflow no longer depends on writing to
/opt.

run: |
WASI_SDK_VERSION="25"
WASI_SDK_DIR="wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux"
curl -sL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_DIR}.tar.gz" | tar xz -C /opt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/release.yml | sed -n '150,165p'

Repository: derodero24/zflate

Length of output: 1030


🏁 Script executed:

grep -n "sha256\|checksum\|hash" .github/workflows/release.yml

Repository: derodero24/zflate

Length of output: 43


🌐 Web query:

wasi-sdk GitHub releases checksums sha256

💡 Result:

The official wasi-sdk GitHub releases page is https://github.com/WebAssembly/wasi-sdk/releases. The latest release is wasi-sdk-32 (prerelease, March 2026). Each release lists downloadable assets (e.g., wasi-sdk-32.0-x86_64-linux.tar.gz) under the Assets section, typically 11-12 files per release for different platforms (Linux x86_64/aarch64, macOS, Windows, deb packages). GitHub does not provide official SHA256 checksums for release assets directly on the releases page. Users must compute checksums themselves after downloading using tools like sha256sum (Linux/macOS) or certutil (Windows). To verify a specific asset: 1. Download from https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-VERSION/wasi-sdk-VERSION.0-PLATFORM.tar.gz 2. Compute SHA256: sha256sum wasi-sdk-VERSION.0-PLATFORM.tar.gz No separate checksum files (e.g., .sha256sum) or GPG signatures are provided in releases. Assets are generated by GitHub Actions (signed commits visible). For verification, rely on GitHub's integrity guarantees or self-computed checksums. Some downstream projects (e.g., FreeBSD ports) maintain their own checksums for specific versions.

Citations:


リリース用 SDK ダウンロードにハッシュ検証を追加してください

外部バイナリの検証なし取得はサプライチェーン上の弱点です。wasi-sdk は公式チェックサムを提供していないため、以下のいずれかを実装してください:

  • リポジトリ内でチェックサム値を固定し、curl で取得後に sha256sum -c で検証する
  • リリース時に検証済みの SHA256 ハッシュをドキュメントに記載し、ワークフロー内で確認する
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml at line 157, The workflow is downloading the
wasi-sdk tarball via the curl | tar pipeline (see the curl command using
WASI_SDK_VERSION and WASI_SDK_DIR) without integrity checks; change it to
download the archive to a file, verify its SHA256 against a pinned hash (either
stored in-repo or passed as a workflow secret/variable) using sha256sum -c (or
equivalent) and only extract with tar after verification succeeds; reference the
variables WASI_SDK_VERSION and WASI_SDK_DIR when locating the downloaded file
and the checksum entry so the verification step fails the job on mismatch.

@derodero24
derodero24 merged commit 9ceb306 into develop Mar 20, 2026
30 checks passed
@derodero24
derodero24 deleted the feat/issue-4-zstd-compress branch March 20, 2026 05:30
derodero24 added a commit that referenced this pull request Mar 20, 2026
* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18
derodero24 added a commit that referenced this pull request Mar 20, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

---------

Co-authored-by: Shintaro Okada <38486312+derodero24@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
derodero24 added a commit that referenced this pull request Mar 24, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Shintaro Okada <38486312+derodero24@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
Co-authored-by: derodero24 <shintaro@payn.io>
derodero24 added a commit that referenced this pull request Mar 24, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

---------

Co-authored-by: Shintaro Okada <38486312+derodero24@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
Co-authored-by: derodero24 <shintaro@payn.io>
derodero24 added a commit that referenced this pull request Apr 3, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Shintaro Okada <38486312+derodero24@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
Co-authored-by: derodero24 <shintaro@payn.io>
derodero24 added a commit that referenced this pull request Apr 3, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
derodero24 added a commit that referenced this pull request Apr 3, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
derodero24 added a commit that referenced this pull request Apr 4, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: update stale package references after rename to @derodero24/comprs (#307)

Update remaining references from old unscoped names to scoped names:

- browser-streaming.js: comprs-wasm32-wasi → @derodero24/comprs-wasm32-wasi
- playground/main.js: import('comprs') → import('@derodero24/comprs')
- playground/package.json: comprs → @derodero24/comprs dependency
- playground/vite.config.js: Vite alias keys updated

This fixes the GitHub Pages deployment failure caused by Vite being
unable to resolve the old unscoped import in browser-streaming.js.

Closes #306

* fix(playground): regenerate lockfile after package rename

The playground's pnpm-lock.yaml was not updated when the dependency
was renamed from comprs to @derodero24/comprs, causing CI to fail
with frozen-lockfile mismatch.

* docs: fix README package references, streaming note, and WASM size (#311)

* fix(docs): update remaining old package name references in README

- Deno import: npm:comprs → npm:@derodero24/comprs
- Subpath imports: comprs/streams → @derodero24/comprs/streams,
  comprs/node → @derodero24/comprs/node
- SSR config: Next.js and Vite external package names

Closes #308

* docs: clarify browser streaming memory behavior

Add note explaining that browser WASM streaming buffers all data in
memory (unlike Node.js which streams incrementally). Also qualify the
"bounded memory" claim in the feature list to specify Node.js.

Closes #309

* docs: add concrete WASM binary size to README

Show approximate optimized WASM size (~2.0 MB raw, ~800 KB gzipped)
directly in README so users can evaluate without navigating to CI.

Closes #310

* fix: update npm badges and sub-package descriptions (#317)

* fix(docs): update npm badges to use scoped package name

The npm version and downloads badges were pointing to the old unscoped
package name "comprs" instead of the scoped "@derodero24/comprs".

Closes #312

* fix: update platform sub-package descriptions to include lz4

All 9 platform sub-packages under npm/ had descriptions listing only
"zstd, gzip, and brotli" but were missing "lz4" which was added to
the root package. Updated to match: "zstd, gzip, brotli, and lz4".

Closes #315

* fix: add missing ESM export and test coverage (#318)

* fix: add missing zstdDecompressWithDictWithCapacity to ESM and browser exports

Add zstdDecompressWithDictWithCapacity to index.mjs destructured exports,
browser-entry.js one-shot re-exports, and ESM smoke test assertions.

Closes #313

* test: add coverage for WithDictWithCapacity functions and Node.js Transform streams

Add tests for zstdDecompressWithDictWithCapacity, brotliDecompressWithDictWithCapacity,
brotliDecompressWithDictWithCapacityAsync, LZ4 Node.js Transform round-trip,
zstd dict Node.js Transform round-trip, and brotli dict Node.js Transform round-trip.

Closes #314

* feat(wasm): replace WASI WASM with wasm-bindgen for browser compatibility (#320)

* refactor(core): extract pure Rust logic into core-lib crate

Split crates/core into two crates:
- crates/core-lib: pure Rust compression logic (no FFI dependencies)
- crates/core: napi-rs thin wrappers delegating to core-lib

This prepares for adding a wasm-bindgen crate that shares the same
core logic, enabling browser usage without SharedArrayBuffer.

Part of #319

* feat(wasm): add wasm-bindgen browser binding crate

Create crates/wasm with wasm-bindgen wrappers over comprs-core,
targeting wasm32-unknown-unknown (no SharedArrayBuffer required).

Includes one-shot and streaming APIs for all algorithms:
zstd, gzip, deflate, brotli, lz4, auto-detect, and crc32.

Part of #319

* feat(wasm): integrate wasm-bindgen build into package distribution

- Add build:wasm-bindgen script using wasm-pack
- Update browser.js to use wasm-bindgen output
- Update browser-streaming.js imports
- Add wasm-bindgen files to npm package
- Update CI with wasm-bindgen build job
- Update release workflow for wasm-bindgen artifacts
- Update playground configuration
- Remove SharedArrayBuffer requirement from README

Part of #319

* fix(ci): resolve CI failures for wasm-bindgen migration

- Remove browser.js from napi generated file diff check (napi build
  regenerates it, but we now maintain it manually for wasm-bindgen)
- Remove unused direct compression deps from napi crate (all logic
  now lives in core-lib)
- Update e2e browser test to use wasm-bindgen output instead of
  WASI browser entry
- Remove COOP/COEP headers from e2e vite config (no longer needed)

Part of #319

* fix(e2e): use manual WASM instantiation for wasm-bindgen in browser test

Vite 8 does not support the TC39 WebAssembly ESM integration proposal.
Import _bg.js directly and instantiate WASM binary manually, following
the same pattern used in rapid-fuzzy.

Also add lz4, crc32, and detectFormat tests to the browser e2e suite.

Part of #319

* fix(e2e): update browser test spec to match wasm-bindgen test suite

Replace streaming tests (removed with WASI migration) with lz4,
crc32, and detectFormat assertions.

Part of #319

* chore: add changeset for v1.0.0 major release (#322)

Closes #321

* chore(release): version packages to v1.0.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
derodero24 added a commit that referenced this pull request Apr 4, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: update stale package references after rename to @derodero24/comprs (#307)

Update remaining references from old unscoped names to scoped names:

- browser-streaming.js: comprs-wasm32-wasi → @derodero24/comprs-wasm32-wasi
- playground/main.js: import('comprs') → import('@derodero24/comprs')
- playground/package.json: comprs → @derodero24/comprs dependency
- playground/vite.config.js: Vite alias keys updated

This fixes the GitHub Pages deployment failure caused by Vite being
unable to resolve the old unscoped import in browser-streaming.js.

Closes #306

* fix(playground): regenerate lockfile after package rename

The playground's pnpm-lock.yaml was not updated when the dependency
was renamed from comprs to @derodero24/comprs, causing CI to fail
with frozen-lockfile mismatch.

* docs: fix README package references, streaming note, and WASM size (#311)

* fix(docs): update remaining old package name references in README

- Deno import: npm:comprs → npm:@derodero24/comprs
- Subpath imports: comprs/streams → @derodero24/comprs/streams,
  comprs/node → @derodero24/comprs/node
- SSR config: Next.js and Vite external package names

Closes #308

* docs: clarify browser streaming memory behavior

Add note explaining that browser WASM streaming buffers all data in
memory (unlike Node.js which streams incrementally). Also qualify the
"bounded memory" claim in the feature list to specify Node.js.

Closes #309

* docs: add concrete WASM binary size to README

Show approximate optimized WASM size (~2.0 MB raw, ~800 KB gzipped)
directly in README so users can evaluate without navigating to CI.

Closes #310

* fix: update npm badges and sub-package descriptions (#317)

* fix(docs): update npm badges to use scoped package name

The npm version and downloads badges were pointing to the old unscoped
package name "comprs" instead of the scoped "@derodero24/comprs".

Closes #312

* fix: update platform sub-package descriptions to include lz4

All 9 platform sub-packages under npm/ had descriptions listing only
"zstd, gzip, and brotli" but were missing "lz4" which was added to
the root package. Updated to match: "zstd, gzip, brotli, and lz4".

Closes #315

* fix: add missing ESM export and test coverage (#318)

* fix: add missing zstdDecompressWithDictWithCapacity to ESM and browser exports

Add zstdDecompressWithDictWithCapacity to index.mjs destructured exports,
browser-entry.js one-shot re-exports, and ESM smoke test assertions.

Closes #313

* test: add coverage for WithDictWithCapacity functions and Node.js Transform streams

Add tests for zstdDecompressWithDictWithCapacity, brotliDecompressWithDictWithCapacity,
brotliDecompressWithDictWithCapacityAsync, LZ4 Node.js Transform round-trip,
zstd dict Node.js Transform round-trip, and brotli dict Node.js Transform round-trip.

Closes #314

* feat(wasm): replace WASI WASM with wasm-bindgen for browser compatibility (#320)

* refactor(core): extract pure Rust logic into core-lib crate

Split crates/core into two crates:
- crates/core-lib: pure Rust compression logic (no FFI dependencies)
- crates/core: napi-rs thin wrappers delegating to core-lib

This prepares for adding a wasm-bindgen crate that shares the same
core logic, enabling browser usage without SharedArrayBuffer.

Part of #319

* feat(wasm): add wasm-bindgen browser binding crate

Create crates/wasm with wasm-bindgen wrappers over comprs-core,
targeting wasm32-unknown-unknown (no SharedArrayBuffer required).

Includes one-shot and streaming APIs for all algorithms:
zstd, gzip, deflate, brotli, lz4, auto-detect, and crc32.

Part of #319

* feat(wasm): integrate wasm-bindgen build into package distribution

- Add build:wasm-bindgen script using wasm-pack
- Update browser.js to use wasm-bindgen output
- Update browser-streaming.js imports
- Add wasm-bindgen files to npm package
- Update CI with wasm-bindgen build job
- Update release workflow for wasm-bindgen artifacts
- Update playground configuration
- Remove SharedArrayBuffer requirement from README

Part of #319

* fix(ci): resolve CI failures for wasm-bindgen migration

- Remove browser.js from napi generated file diff check (napi build
  regenerates it, but we now maintain it manually for wasm-bindgen)
- Remove unused direct compression deps from napi crate (all logic
  now lives in core-lib)
- Update e2e browser test to use wasm-bindgen output instead of
  WASI browser entry
- Remove COOP/COEP headers from e2e vite config (no longer needed)

Part of #319

* fix(e2e): use manual WASM instantiation for wasm-bindgen in browser test

Vite 8 does not support the TC39 WebAssembly ESM integration proposal.
Import _bg.js directly and instantiate WASM binary manually, following
the same pattern used in rapid-fuzzy.

Also add lz4, crc32, and detectFormat tests to the browser e2e suite.

Part of #319

* fix(e2e): update browser test spec to match wasm-bindgen test suite

Replace streaming tests (removed with WASI migration) with lz4,
crc32, and detectFormat assertions.

Part of #319

* chore: add changeset for v1.0.0 major release (#322)

Closes #321

* chore(release): version packages to v1.0.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: address post-release bugs in v1.0.0 (#327)

- Add missing zstdDecompressWithDictWithCapacityAsync to index.mjs
- Sync crates/core-lib and crates/wasm version to 1.0.0
- Fix sync-cargo-version.js to cover all three Cargo.toml files
- Add missing async functions to README API list
- Add missing ESM smoke test assertion

Closes #326

* chore: add changeset for v1.0.1 patch release (#328)

Covers the bug fixes from #327:
- Missing zstdDecompressWithDictWithCapacityAsync ESM export
- Version mismatch in core-lib and wasm Cargo.toml
- Missing async functions in README API list

* chore(release): version packages to v1.0.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
derodero24 added a commit that referenced this pull request Apr 4, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: update stale package references after rename to @derodero24/comprs (#307)

Update remaining references from old unscoped names to scoped names:

- browser-streaming.js: comprs-wasm32-wasi → @derodero24/comprs-wasm32-wasi
- playground/main.js: import('comprs') → import('@derodero24/comprs')
- playground/package.json: comprs → @derodero24/comprs dependency
- playground/vite.config.js: Vite alias keys updated

This fixes the GitHub Pages deployment failure caused by Vite being
unable to resolve the old unscoped import in browser-streaming.js.

Closes #306

* fix(playground): regenerate lockfile after package rename

The playground's pnpm-lock.yaml was not updated when the dependency
was renamed from comprs to @derodero24/comprs, causing CI to fail
with frozen-lockfile mismatch.

* docs: fix README package references, streaming note, and WASM size (#311)

* fix(docs): update remaining old package name references in README

- Deno import: npm:comprs → npm:@derodero24/comprs
- Subpath imports: comprs/streams → @derodero24/comprs/streams,
  comprs/node → @derodero24/comprs/node
- SSR config: Next.js and Vite external package names

Closes #308

* docs: clarify browser streaming memory behavior

Add note explaining that browser WASM streaming buffers all data in
memory (unlike Node.js which streams incrementally). Also qualify the
"bounded memory" claim in the feature list to specify Node.js.

Closes #309

* docs: add concrete WASM binary size to README

Show approximate optimized WASM size (~2.0 MB raw, ~800 KB gzipped)
directly in README so users can evaluate without navigating to CI.

Closes #310

* fix: update npm badges and sub-package descriptions (#317)

* fix(docs): update npm badges to use scoped package name

The npm version and downloads badges were pointing to the old unscoped
package name "comprs" instead of the scoped "@derodero24/comprs".

Closes #312

* fix: update platform sub-package descriptions to include lz4

All 9 platform sub-packages under npm/ had descriptions listing only
"zstd, gzip, and brotli" but were missing "lz4" which was added to
the root package. Updated to match: "zstd, gzip, brotli, and lz4".

Closes #315

* fix: add missing ESM export and test coverage (#318)

* fix: add missing zstdDecompressWithDictWithCapacity to ESM and browser exports

Add zstdDecompressWithDictWithCapacity to index.mjs destructured exports,
browser-entry.js one-shot re-exports, and ESM smoke test assertions.

Closes #313

* test: add coverage for WithDictWithCapacity functions and Node.js Transform streams

Add tests for zstdDecompressWithDictWithCapacity, brotliDecompressWithDictWithCapacity,
brotliDecompressWithDictWithCapacityAsync, LZ4 Node.js Transform round-trip,
zstd dict Node.js Transform round-trip, and brotli dict Node.js Transform round-trip.

Closes #314

* feat(wasm): replace WASI WASM with wasm-bindgen for browser compatibility (#320)

* refactor(core): extract pure Rust logic into core-lib crate

Split crates/core into two crates:
- crates/core-lib: pure Rust compression logic (no FFI dependencies)
- crates/core: napi-rs thin wrappers delegating to core-lib

This prepares for adding a wasm-bindgen crate that shares the same
core logic, enabling browser usage without SharedArrayBuffer.

Part of #319

* feat(wasm): add wasm-bindgen browser binding crate

Create crates/wasm with wasm-bindgen wrappers over comprs-core,
targeting wasm32-unknown-unknown (no SharedArrayBuffer required).

Includes one-shot and streaming APIs for all algorithms:
zstd, gzip, deflate, brotli, lz4, auto-detect, and crc32.

Part of #319

* feat(wasm): integrate wasm-bindgen build into package distribution

- Add build:wasm-bindgen script using wasm-pack
- Update browser.js to use wasm-bindgen output
- Update browser-streaming.js imports
- Add wasm-bindgen files to npm package
- Update CI with wasm-bindgen build job
- Update release workflow for wasm-bindgen artifacts
- Update playground configuration
- Remove SharedArrayBuffer requirement from README

Part of #319

* fix(ci): resolve CI failures for wasm-bindgen migration

- Remove browser.js from napi generated file diff check (napi build
  regenerates it, but we now maintain it manually for wasm-bindgen)
- Remove unused direct compression deps from napi crate (all logic
  now lives in core-lib)
- Update e2e browser test to use wasm-bindgen output instead of
  WASI browser entry
- Remove COOP/COEP headers from e2e vite config (no longer needed)

Part of #319

* fix(e2e): use manual WASM instantiation for wasm-bindgen in browser test

Vite 8 does not support the TC39 WebAssembly ESM integration proposal.
Import _bg.js directly and instantiate WASM binary manually, following
the same pattern used in rapid-fuzzy.

Also add lz4, crc32, and detectFormat tests to the browser e2e suite.

Part of #319

* fix(e2e): update browser test spec to match wasm-bindgen test suite

Replace streaming tests (removed with WASI migration) with lz4,
crc32, and detectFormat assertions.

Part of #319

* chore: add changeset for v1.0.0 major release (#322)

Closes #321

* chore(release): version packages to v1.0.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: address post-release bugs in v1.0.0 (#327)

- Add missing zstdDecompressWithDictWithCapacityAsync to index.mjs
- Sync crates/core-lib and crates/wasm version to 1.0.0
- Fix sync-cargo-version.js to cover all three Cargo.toml files
- Add missing async functions to README API list
- Add missing ESM smoke test assertion

Closes #326

* chore: add changeset for v1.0.1 patch release (#328)

Covers the bug fixes from #327:
- Missing zstdDecompressWithDictWithCapacityAsync ESM export
- Version mismatch in core-lib and wasm Cargo.toml
- Missing async functions in README API list

* chore(release): version packages to v1.0.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(playground): fix GitHub Pages deployment with wasm-bindgen (#333)

Root cause: napi build/version overwrites browser.js to point at WASI
WASM, and Vite 8 rejects ESM WASM imports from wasm-pack bundler output.

Fixes:
- Add scripts/patch-browser-js.js to restore browser.js after napi
  overwrites it (runs in changeset:version and build:wasm-bindgen)
- Add playground/wasm-loader.js for manual WASM instantiation
  (Vite 8 ESM WASM workaround, same pattern as rapid-fuzzy)
- Update playground vite.config.js to use wasm-loader
- Remove obsolete Buffer polyfill from playground/main.js

Closes #332

* chore: improve npm discoverability and update CONTRIBUTING.md (#334)

- Add keywords: compress, wasm-bindgen, native, web-streams
- Update CONTRIBUTING.md project structure for three-crate architecture
- Fix dead "GitHub Discussion" reference (Discussions is disabled)

* perf: v1.1.0 performance improvements (#348)

* perf(gzip): use ISIZE field for decompression buffer pre-allocation

Read the gzip footer's ISIZE field (last 4 bytes, little-endian uint32)
to pre-allocate the exact output buffer size instead of using a fixed
4x heuristic. This eliminates Vec re-allocations for most decompression
workloads.

Falls back to 4x heuristic when ISIZE is 0 (empty data or >4GB wrapping)
or when input is too small to be valid gzip.

Closes #335

* perf(zstd): enable multi-threaded compression (zstdmt)

Add zstdmt feature flag to comprs-core, enabled by default in the
napi crate (Node.js) but not in the wasm crate (single-threaded).

This allows zstd to use multiple threads for compression of large data,
providing 2-4x speedup on multi-core systems.

Closes #336

* perf(streaming): reuse internal buffers instead of allocating per transform

Add a reusable output_buf field to all zstd streaming context structs.
Vec::resize reuses existing capacity after the first call, eliminating
the 128KB allocation + zeroing overhead on subsequent transform calls.

The gzip, brotli, and lz4 streaming contexts delegate buffering to
their underlying encoder/decoder inner Vec<u8> (drained via mem::take),
so they do not have the same per-call allocation pattern and are
unaffected.

Closes #337

* perf(zstd): add comparison benchmark against node:zlib

Add zstd.compare.bench.ts comparing comprs zstd performance against
Node.js 22+ built-in zlib.zstdCompressSync/zstdDecompressSync.

Closes #338

* perf(streaming): add Transform stream throughput benchmarks

Add streaming.compare.bench.ts measuring Node.js Transform stream
throughput for comprs vs node:zlib, with various chunk sizes.

Closes #340

* chore: add changeset for v1.1.0 performance improvements (#349)

Covers #335 (gzip ISIZE), #336 (zstdmt), #337 (streaming buffer reuse).

* chore(release): version packages to v1.1.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
derodero24 added a commit that referenced this pull request Apr 12, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: update stale package references after rename to @derodero24/comprs (#307)

Update remaining references from old unscoped names to scoped names:

- browser-streaming.js: comprs-wasm32-wasi → @derodero24/comprs-wasm32-wasi
- playground/main.js: import('comprs') → import('@derodero24/comprs')
- playground/package.json: comprs → @derodero24/comprs dependency
- playground/vite.config.js: Vite alias keys updated

This fixes the GitHub Pages deployment failure caused by Vite being
unable to resolve the old unscoped import in browser-streaming.js.

Closes #306

* fix(playground): regenerate lockfile after package rename

The playground's pnpm-lock.yaml was not updated when the dependency
was renamed from comprs to @derodero24/comprs, causing CI to fail
with frozen-lockfile mismatch.

* docs: fix README package references, streaming note, and WASM size (#311)

* fix(docs): update remaining old package name references in README

- Deno import: npm:comprs → npm:@derodero24/comprs
- Subpath imports: comprs/streams → @derodero24/comprs/streams,
  comprs/node → @derodero24/comprs/node
- SSR config: Next.js and Vite external package names

Closes #308

* docs: clarify browser streaming memory behavior

Add note explaining that browser WASM streaming buffers all data in
memory (unlike Node.js which streams incrementally). Also qualify the
"bounded memory" claim in the feature list to specify Node.js.

Closes #309

* docs: add concrete WASM binary size to README

Show approximate optimized WASM size (~2.0 MB raw, ~800 KB gzipped)
directly in README so users can evaluate without navigating to CI.

Closes #310

* fix: update npm badges and sub-package descriptions (#317)

* fix(docs): update npm badges to use scoped package name

The npm version and downloads badges were pointing to the old unscoped
package name "comprs" instead of the scoped "@derodero24/comprs".

Closes #312

* fix: update platform sub-package descriptions to include lz4

All 9 platform sub-packages under npm/ had descriptions listing only
"zstd, gzip, and brotli" but were missing "lz4" which was added to
the root package. Updated to match: "zstd, gzip, brotli, and lz4".

Closes #315

* fix: add missing ESM export and test coverage (#318)

* fix: add missing zstdDecompressWithDictWithCapacity to ESM and browser exports

Add zstdDecompressWithDictWithCapacity to index.mjs destructured exports,
browser-entry.js one-shot re-exports, and ESM smoke test assertions.

Closes #313

* test: add coverage for WithDictWithCapacity functions and Node.js Transform streams

Add tests for zstdDecompressWithDictWithCapacity, brotliDecompressWithDictWithCapacity,
brotliDecompressWithDictWithCapacityAsync, LZ4 Node.js Transform round-trip,
zstd dict Node.js Transform round-trip, and brotli dict Node.js Transform round-trip.

Closes #314

* feat(wasm): replace WASI WASM with wasm-bindgen for browser compatibility (#320)

* refactor(core): extract pure Rust logic into core-lib crate

Split crates/core into two crates:
- crates/core-lib: pure Rust compression logic (no FFI dependencies)
- crates/core: napi-rs thin wrappers delegating to core-lib

This prepares for adding a wasm-bindgen crate that shares the same
core logic, enabling browser usage without SharedArrayBuffer.

Part of #319

* feat(wasm): add wasm-bindgen browser binding crate

Create crates/wasm with wasm-bindgen wrappers over comprs-core,
targeting wasm32-unknown-unknown (no SharedArrayBuffer required).

Includes one-shot and streaming APIs for all algorithms:
zstd, gzip, deflate, brotli, lz4, auto-detect, and crc32.

Part of #319

* feat(wasm): integrate wasm-bindgen build into package distribution

- Add build:wasm-bindgen script using wasm-pack
- Update browser.js to use wasm-bindgen output
- Update browser-streaming.js imports
- Add wasm-bindgen files to npm package
- Update CI with wasm-bindgen build job
- Update release workflow for wasm-bindgen artifacts
- Update playground configuration
- Remove SharedArrayBuffer requirement from README

Part of #319

* fix(ci): resolve CI failures for wasm-bindgen migration

- Remove browser.js from napi generated file diff check (napi build
  regenerates it, but we now maintain it manually for wasm-bindgen)
- Remove unused direct compression deps from napi crate (all logic
  now lives in core-lib)
- Update e2e browser test to use wasm-bindgen output instead of
  WASI browser entry
- Remove COOP/COEP headers from e2e vite config (no longer needed)

Part of #319

* fix(e2e): use manual WASM instantiation for wasm-bindgen in browser test

Vite 8 does not support the TC39 WebAssembly ESM integration proposal.
Import _bg.js directly and instantiate WASM binary manually, following
the same pattern used in rapid-fuzzy.

Also add lz4, crc32, and detectFormat tests to the browser e2e suite.

Part of #319

* fix(e2e): update browser test spec to match wasm-bindgen test suite

Replace streaming tests (removed with WASI migration) with lz4,
crc32, and detectFormat assertions.

Part of #319

* chore: add changeset for v1.0.0 major release (#322)

Closes #321

* chore(release): version packages to v1.0.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: address post-release bugs in v1.0.0 (#327)

- Add missing zstdDecompressWithDictWithCapacityAsync to index.mjs
- Sync crates/core-lib and crates/wasm version to 1.0.0
- Fix sync-cargo-version.js to cover all three Cargo.toml files
- Add missing async functions to README API list
- Add missing ESM smoke test assertion

Closes #326

* chore: add changeset for v1.0.1 patch release (#328)

Covers the bug fixes from #327:
- Missing zstdDecompressWithDictWithCapacityAsync ESM export
- Version mismatch in core-lib and wasm Cargo.toml
- Missing async functions in README API list

* chore(release): version packages to v1.0.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(playground): fix GitHub Pages deployment with wasm-bindgen (#333)

Root cause: napi build/version overwrites browser.js to point at WASI
WASM, and Vite 8 rejects ESM WASM imports from wasm-pack bundler output.

Fixes:
- Add scripts/patch-browser-js.js to restore browser.js after napi
  overwrites it (runs in changeset:version and build:wasm-bindgen)
- Add playground/wasm-loader.js for manual WASM instantiation
  (Vite 8 ESM WASM workaround, same pattern as rapid-fuzzy)
- Update playground vite.config.js to use wasm-loader
- Remove obsolete Buffer polyfill from playground/main.js

Closes #332

* chore: improve npm discoverability and update CONTRIBUTING.md (#334)

- Add keywords: compress, wasm-bindgen, native, web-streams
- Update CONTRIBUTING.md project structure for three-crate architecture
- Fix dead "GitHub Discussion" reference (Discussions is disabled)

* perf: v1.1.0 performance improvements (#348)

* perf(gzip): use ISIZE field for decompression buffer pre-allocation

Read the gzip footer's ISIZE field (last 4 bytes, little-endian uint32)
to pre-allocate the exact output buffer size instead of using a fixed
4x heuristic. This eliminates Vec re-allocations for most decompression
workloads.

Falls back to 4x heuristic when ISIZE is 0 (empty data or >4GB wrapping)
or when input is too small to be valid gzip.

Closes #335

* perf(zstd): enable multi-threaded compression (zstdmt)

Add zstdmt feature flag to comprs-core, enabled by default in the
napi crate (Node.js) but not in the wasm crate (single-threaded).

This allows zstd to use multiple threads for compression of large data,
providing 2-4x speedup on multi-core systems.

Closes #336

* perf(streaming): reuse internal buffers instead of allocating per transform

Add a reusable output_buf field to all zstd streaming context structs.
Vec::resize reuses existing capacity after the first call, eliminating
the 128KB allocation + zeroing overhead on subsequent transform calls.

The gzip, brotli, and lz4 streaming contexts delegate buffering to
their underlying encoder/decoder inner Vec<u8> (drained via mem::take),
so they do not have the same per-call allocation pattern and are
unaffected.

Closes #337

* perf(zstd): add comparison benchmark against node:zlib

Add zstd.compare.bench.ts comparing comprs zstd performance against
Node.js 22+ built-in zlib.zstdCompressSync/zstdDecompressSync.

Closes #338

* perf(streaming): add Transform stream throughput benchmarks

Add streaming.compare.bench.ts measuring Node.js Transform stream
throughput for comprs vs node:zlib, with various chunk sizes.

Closes #340

* chore: add changeset for v1.1.0 performance improvements (#349)

Covers #335 (gzip ISIZE), #336 (zstdmt), #337 (streaming buffer reuse).

* chore(release): version packages to v1.1.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore(deps): update taiki-e/install-action digest to 7a562df (#353)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update embarkstudios/cargo-deny-action digest to 175dc7f (#354)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to 97a5807 (#355)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.8 (#361)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency lefthook to v2.1.5 (#359)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.11 (#356)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.8 (#360)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/cli to v3.6.1 (#357)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.3 (#358)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.4 (#362)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): set minimumReleaseAge to 3 days in Renovate (#363)

Prevent adopting newly released packages that may be yanked or have
undiscovered issues. This applies globally to npm, Cargo, and GitHub
Actions dependencies.

* feat(middleware): add Express HTTP compression middleware (#364)

* feat(middleware): add Express HTTP compression middleware

Add @derodero24/comprs-middleware — the first Express middleware with
zstd support. Includes Accept-Encoding negotiation, configurable
algorithm priority, threshold-based skip, and Content-Type filtering.

- Set up pnpm workspace for multi-package monorepo
- Implement negotiation, compression, and middleware modules
- Add 31 unit and integration tests
- Update CI to lint/test the middleware package
- Update release workflow for multi-package publishing

Closes #343

* fix(middleware): address CodeRabbit review feedback

- Make isCompressibleType return false for missing Content-Type
- Add stream error handler to prevent unhandled exceptions
- Remove wildcard fallback that could override explicit rejections
- Separate middleware publish job for independent releases
- Limit middleware CI tests to ubuntu/node24 only
- Narrow biome noBarrelFile override to middleware package
- Refactor test helper to support HEAD requests
- Add HEAD request skip test
- Remove "first" claim from README descriptions

* fix(middleware): fix description, docs, and add typescript devDep

- Remove "First" claim from package.json description
- Update filter JSDoc to reflect Content-Type skip behavior
- Add typescript to devDependencies for standalone builds
- Fix filter default description in README table

* fix(middleware): update lockfile for typescript devDependency

* fix(middleware): normalize write/end args and preserve callbacks

Properly handle all overload forms of res.write() and res.end():
- Normalize chunk, encoding, and callback from variadic args
- Pass callbacks through to compressStream.write/end
- Handle res.end(callback) without treating function as chunk body
- Use null-check (!=) instead of truthy for chunk to handle empty strings

* chore(release): version packages (#365)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): check all workspace packages for unpublished versions (#367)

* fix(ci): check all workspace packages for unpublished versions

The release PR creation step only checked the core package version,
preventing middleware-only releases from triggering a develop→main PR.

Now checks both @derodero24/comprs and @derodero24/comprs-middleware
versions against npm, creating a release PR if any package is unpublished.

Closes #366

* fix(ci): read package names from manifest and handle npm errors

- Read package names from package.json instead of hardcoding
- Distinguish E404 (unpublished) from network/auth errors
- Fail the job on unexpected npm view errors instead of silently skipping

* feat(middleware): add Fastify and Hono support via subpath exports (#370)

* feat(middleware): add Fastify and Hono support via subpath exports

Restructure @derodero24/comprs-middleware from Express-only to
multi-framework with subpath exports:

- ./express — Express/Connect middleware (refactored from root export)
- ./fastify — Fastify plugin (onSend hook with stream compression)
- ./hono    — Hono middleware (sync compression via Web Standards API)
- .         — shared utilities (negotiate, types)

Extract framework-agnostic logic into shared.ts. Add 21 new tests
for Fastify (10) and Hono (11), total 53 tests across 4 test files.

Closes #369

* fix(middleware): address CodeRabbit review feedback

- Handle Vary: * without appending Accept-Encoding (RFC 7231)
- Extract shouldSkip helper in Hono adapter to reduce complexity
- Add Content-Length early threshold check in Hono to avoid body read
- Log compression pipeline errors in Fastify (skip premature close)
- Add stream compression behavior comment in Fastify
- Add identity-only Accept-Encoding test for Fastify
- Fix README import examples to use separate blocks per framework

* fix(middleware): case-insensitive Cache-Control check, Hono error handling

- Normalize Cache-Control to lowercase before checking no-transform (all adapters)
- Set Vary header before HEAD early return in Hono
- Wrap Hono body read + compression in try/catch for graceful fallback

* fix(ci): use manifest name and E404 check in publish-middleware job (#373)

Read package name from package.json instead of hardcoding.
Distinguish E404 (unpublished) from network/auth errors to prevent
silent failures during middleware publishing.

Closes #372

* chore(release): version packages (#371)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Shintaro Okada <38486312+derodero24@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
Co-authored-by: derodero24 <shintaro@payn.io>
derodero24 added a commit that referenced this pull request May 4, 2026
* Set up napi-rs build pipeline (#9)

* chore(core): add Rust crate skeleton with napi-rs bindings

Minimal `zflate` crate with a single `version()` export to verify the
napi-rs build pipeline works end-to-end.

- crates/core/Cargo.toml: cdylib crate with napi-rs v3
- crates/core/build.rs: napi_build setup
- crates/core/src/lib.rs: version() function

* chore: add package.json with napi-rs configuration

Configure the npm package with 9 build targets (macOS, Linux, Windows,
WASM), dev dependencies for tooling (Biome, Vitest, TypeScript,
Changesets, CodSpeed), and build/test/lint scripts.

* chore: add build scripts and module entry points

- scripts/optimize-wasm.js: post-process WASM with wasm-opt
- index.mjs / index.d.mts: ESM entry points
- index.js / index.d.ts / browser.js: napi-rs generated files (force-tracked)

* Add CI workflow (#10)

* test: add initial test suite and ESM smoke test

- __test__/index.spec.ts: Vitest spec verifying version() export
- __test__/esm-import.mjs: ESM import smoke test for CI

* chore: update biome and tsconfig for test files

- biome.json: allow console in ESM smoke test
- tsconfig.json: format with Biome

* ci: add CI workflow

Full CI pipeline adapted from rapid-fuzzy:
- Lint (Biome, TypeScript, publint, generated file verification)
- Dependency audit (npm + cargo-deny)
- Rust lint (fmt + clippy) and test
- Cross-platform build (9 targets: macOS, Linux, Windows, WASM)
- Node.js test matrix (3 versions × 3 OSes)
- Coverage (Rust via cargo-llvm-cov + JS via Vitest, uploaded to Codecov)

* Add release and automation workflows (#11)

* ci: add release workflow with Cargo.toml version sync

Changesets-driven release flow:
- On develop push: create version PR, then release PR (develop → main)
- On main push: build all 9 targets and publish to npm with provenance
- scripts/sync-cargo-version.js: keep Cargo.toml version in sync with
  package.json after changeset version bumps

* ci: add CodeQL security analysis

Analyze JavaScript/TypeScript and Rust code for security vulnerabilities.
Runs on PRs, develop pushes, and weekly schedule (Monday 6 AM UTC).

* ci: add CodSpeed benchmark workflow

Continuous performance tracking via CodSpeed. Runs JS benchmarks on
develop pushes and PRs when crate or bench files change.
Rust benchmarks will be added when the bench crate is introduced.

* ci: add self-hosted Renovate workflow

Automated dependency updates via self-hosted Renovate bot.
Runs weekly on Sunday 19:00 UTC (Monday 4:00 JST).

* test(bench): add initial benchmark file

Minimal benchmark to satisfy CodSpeed workflow. Real compression
benchmarks will be added alongside algorithm implementations.

* Implement zstd compression and decompression (#12)

* feat(zstd): implement zstd compression and decompression

Add zstdCompress() and zstdDecompress() functions powered by the zstd
C library via the zstd Rust crate.

- Compression levels 1-22 (default: 3)
- Auto-detection of frame content size for efficient decompression
- zstdDecompressWithCapacity() for data exceeding 256 MB default limit
- Rust unit tests for round-trip, empty data, large data, and levels

* test(zstd): add JS tests and benchmarks for zstd

- __test__/zstd.spec.ts: 11 tests covering round-trip, levels, errors
- __test__/esm-import.mjs: updated with zstd round-trip smoke test
- __test__/index.bench.ts: compress/decompress benchmarks at 150B/10KB/1MB
- index.mjs: export zstd functions

* chore: update napi-rs generated files and add changeset

- Regenerate index.js, index.d.ts, browser.js with zstd exports
- Add changeset for minor version bump

* ci: add WASI SDK setup for zstd WASM builds

The zstd crate compiles C code via zstd-sys, which requires a proper
WASI sysroot when targeting wasm32-wasip1-threads. Without it, clang
picks up system glibc headers and fails with missing
'bits/libc-header-start.h'.

Use bytecodealliance/actions/wasi-sdk/setup to install WASI SDK and
set CC, AR, CFLAGS, and WASI_SYSROOT environment variables for the
WASM build step in both CI and release workflows.

* Fix CodeRabbit review findings from #12 (#22)

* fix(zstd): validate capacity parameter in zstdDecompressWithCapacity

Reject negative, NaN, and Infinity values before casting f64 to usize.
Without validation, a negative f64 wraps to a very large usize, which
could cause memory exhaustion.

Closes #17

* fix(ci): use RUNNER_TEMP for WASI SDK installation

Replace /opt with $RUNNER_TEMP for WASI SDK extraction. /opt may not
be writable on all GitHub Actions runner configurations.
Also switch curl from -sL to -fsSL to fail on HTTP errors.

Closes #18

* docs: add SECURITY.md (#23)

* docs: add SECURITY.md

* docs: address review feedback on SECURITY.md

- Replace vague "extremely large size" with "massive size" for clarity
- Unify SLA day units by removing "business" qualifier from initial
  assessment timeline for consistency

* docs: add CONTRIBUTING.md (#24)

* test(zstd): expand test coverage and benchmarks (#25)

* test(zstd): expand test coverage and benchmarks

Add comprehensive JS tests covering compression level boundaries (0,
negative, 22), decompressWithCapacity edge cases (insufficient, oversized,
invalid inputs), and round-trip verification with random data.

Expand benchmarks with random (incompressible) and realistic (JSON, text)
data variants alongside existing patterned data.

Update Rust doc comments to document negative compression levels and
level 0 behavior. Add Rust tests for boundary levels. Update changeset
to include all public API functions.

Closes #19

* chore: update napi-rs generated type definitions

Regenerate index.d.ts to reflect updated doc comments for
zstd_compress documenting negative levels and level 0 behavior.

* feat(zstd): add streaming compression/decompression API (#26)

Implement streaming zstd compression and decompression using the Web
Streams API (TransformStream). The Rust layer exposes ZstdCompressContext
and ZstdDecompressContext classes with transform/flush/finish methods
using zstd::stream::raw for stateful chunked processing.

The JS layer wraps these in TransformStream via createZstdCompressStream()
and createZstdDecompressStream(), available from both ESM and CJS entry
points. Streaming output is fully interoperable with one-shot APIs.

Add comprehensive tests covering chunked round-trips, various chunk
sizes, large data (1MB), random data, and one-shot/streaming interop.

Closes #5

* docs: add README and verify npm package readiness (#28)

* docs: add README and verify npm package readiness

Add comprehensive README with project description, installation guide,
quick-start examples (one-shot and streaming), full API reference,
supported algorithms table, platform compatibility matrix, and
benchmark results.

Verify npm package with publint (no errors). Package includes all
required files: index.js, index.mjs, index.d.ts, index.d.mts,
streams.js, streams.d.ts, browser.js, LICENSE, and README.md.

Closes #8

* docs(readme): address review feedback

- Add bun installation instructions
- Replace streaming example with self-contained runnable snippet

* fix(ci): resolve release workflow failures (#29)

* fix(ci): resolve release workflow failures

- Generate npm platform package directories for all 9 targets
- Add optionalDependencies to root package.json for platform binaries
- Fix changeset version command argument parsing in release workflow
  by using a dedicated npm script instead of inline shell command

Closes #27

* fix(ci): remove optionalDependencies from committed package.json

The optionalDependencies for platform-specific packages are added
at publish time by the prepublishOnly hook (napi prepublish -t npm),
not in the committed source. Having them in package.json breaks
pnpm install --frozen-lockfile since the packages are not yet on npm.

* chore(release): version packages (#30)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(ci): regenerate napi bindings for v0.2.0 and fix version workflow (#34)

- Regenerate index.js with v0.2.0 version strings
- Update npm platform package versions to 0.2.0
- Add Rust toolchain to version job for napi build during version bump
- Add napi version, build, and format to changeset:version script
- Fix version test to read from package.json dynamically

* feat(gzip): add gzip and deflate compression support (#33)

Add gzip and raw deflate compression/decompression with one-shot and
streaming APIs. Includes interoperability tests with Node.js built-in
zlib module.

Closes #6

* feat(brotli): add brotli compression support (#32)

* feat(brotli): add brotli compression support

Add brotli compression/decompression with one-shot and streaming APIs.
Quality levels 0-11 (default: 6). Includes interoperability tests with
Node.js built-in zlib brotli module.

Closes #7

* test(brotli): add brotli compression benchmarks

Add brotli compress/decompress benchmarks matching the zstd benchmark
structure for patterned, random, and realistic data at various sizes.

* test(gzip): increase timeout for 1MB round-trip tests

Extend timeout from 5s to 30s for gzip and deflate 1MB data tests
to prevent CI failures on slower runners.

* test: increase timeout for all 1MB tests

Set 30s timeout on all 1MB data round-trip and streaming tests to
prevent failures on slower CI runners (Windows).

* docs(readme): update API reference for gzip and brotli (#36)

Add gzip, deflate, and brotli to the API reference tables. Update
supported algorithms table to reflect all three algorithms are now
available with both one-shot and streaming APIs.

* chore(deps): update taiki-e/install-action digest to c12d62a (#20)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(exports): add ./streams subpath export to package.json (#58)

Add a `./streams` subpath export so users can import streaming APIs
directly via `import { ... } from 'zflate/streams'`.

Closes #48

* fix(gzip): add compression level validation for gzip and deflate (#60)

Validate that the compression level parameter is within the valid range
(0-9) for gzip_compress, deflate_compress, GzipCompressContext, and
DeflateCompressContext. Return InvalidArg error if out of range, matching
the existing validation pattern in brotli_compress.

Closes #38

* fix(gzip): add decompression size limits to gzip and deflate (#64)

* fix(gzip): add decompression size limits to gzip and deflate

Add MAX_DECOMPRESSED_SIZE (256 MB) to gzip_decompress and
deflate_decompress to prevent zip bomb attacks. Both functions now use
chunked reading with size checking instead of unbounded read_to_end().

Add gzipDecompressWithCapacity and deflateDecompressWithCapacity for
explicit control over the maximum decompressed size, matching the
existing zstd and brotli patterns.

Closes #37

* chore(napi): regenerate bindings for new WithCapacity functions

* feat(api): accept Uint8Array input in all compression functions (#69)

Change all input parameters from Buffer to Either<Buffer, Uint8Array>
across all one-shot and streaming functions. This generates
Buffer | Uint8Array TypeScript types, enabling browser/Deno/Bun users
to pass Uint8Array directly without type errors.

Return types remain Buffer. No runtime behavior changes for existing
Buffer usage.

Closes #42

* docs: enhance README with examples, comparison table, and migration guides (#63)

* test: add cargo-fuzz targets for decompression fuzzing (#71)

* test: add edge case and error handling tests (#65)

* test: expand Node.js zlib interop tests to brotli and zstd (#67)

* ci: add hand-written JS files to workflow path filters (#56)

* ci(codeql): suppress rust/access-invalid-pointer false positives (#57)

* fix(dx): add ./streams subpath export to package.json (#59)

* ci: add cargo-audit to dependency audit pipeline (#61)

* feat(core): add unified auto-detect decompression API (#72)

Add decompress() function that auto-detects compression format from
magic bytes (zstd: 0xFD2FB528, gzip: 0x1F8B) and falls back to brotli
heuristic detection. Add detectFormat() for format identification
without decompression.

Supported formats: zstd, gzip, brotli. Raw deflate cannot be
auto-detected due to lack of magic bytes.

Closes #16

* ci(dx): add @arethetypeswrong/cli type validation (#62)

* perf(bench): add competitive benchmarks across all algorithms (#70)

* perf(wasm): add WASM binary size reporting to CI (#66)

* fix(exports): add missing ESM exports for new functions (#74)

Add decompress, detectFormat, gzipDecompressWithCapacity, and
deflateDecompressWithCapacity to index.mjs ESM export list. These
functions were available via CJS but missing from ESM imports.

Update ESM smoke test to cover all WithCapacity functions, decompress,
and detectFormat.

Closes #73

* feat(node): add Node.js stream.Transform compatibility layer (#76)

Add `zflate/node` subpath export with stream.Transform factories for all
compression algorithms (zstd, gzip, deflate, brotli), enabling use with
Node.js stream.pipeline() and pipe-based workflows.

Closes #43

* fix(brotli): use consistent error message for capacity exceeded (#78)

Change brotli_decompress_with_capacity error message from
"Destination buffer is too small" to "brotli decompress exceeded
maximum size of {} bytes" to match the pattern used by all other
decompress functions.

Closes #77

* fix(docs): remove merge conflict marker and stale src/ references (#80)

* feat(core): add async compression/decompression API (#82)

Add async versions of all one-shot compression/decompression functions
using napi-rs AsyncTask with the Task trait. This runs compression on
the libuv thread pool without blocking the Node.js event loop.

New functions: zstdCompressAsync, zstdDecompressAsync,
gzipCompressAsync, gzipDecompressAsync, deflateCompressAsync,
deflateDecompressAsync, brotliCompressAsync, brotliDecompressAsync.

Closes #40

* feat(zstd): add dictionary compression API (#83)

Add zstd dictionary training, compression, and decompression functions:
- zstdTrainDictionary: train a dictionary from sample data
- zstdCompressWithDict: compress with a pre-trained dictionary
- zstdDecompressWithDict: decompress data compressed with a dictionary

Enable the zdict_builder feature for the zstd crate to support
dictionary training. Include Rust unit tests and Vitest integration
tests verifying round-trip correctness and improved compression ratios
for small, similar data.

Closes #41

* docs: remove Cloudflare Workers from supported platforms (#91)

* ci: include Node.js WASM loader files in CI artifact (#90)

* test(wasm): add Node.js WASM compatibility test with native parity verification (#92)

* refactor(core): introduce thiserror for structured error types (#93)

Replace manual `Error::new(Status::..., ...)` calls with structured
`ZflateError` enum variants across all compression modules. Error
messages remain identical to preserve backward compatibility.

Closes #51

* test(deno): add Deno WASM smoke test (#96)

* test(bun): add Bun WASM smoke test (#95)

* test(browser): add Playwright browser WASM test (#94)

* ci(security): tighten cargo-deny bans and sources to deny mode (#108)

* ci: pin dtolnay/rust-toolchain to SHA digest (#107)

* fix(core): add decompression size limits to streaming contexts (#109)

Add cumulative output size tracking to all streaming decompression
contexts (ZstdDecompressContext, GzipDecompressContext,
DeflateDecompressContext, BrotliDecompressContext).

Each context now enforces MAX_DECOMPRESSED_SIZE (256 MB) across
transform() calls, preventing decompression bomb attacks that could
cause unbounded memory growth.

Closes #97

* feat(core): narrow detectFormat return type to string literal union (#111)

Use napi-rs string_enum to return CompressionFormat instead of plain
string, generating a const enum with 'zstd' | 'gzip' | 'brotli' |
'unknown' values in TypeScript.

Closes #103

* perf(core): optimize buffer allocation in compression and decompression (#110)

* perf(core): optimize buffer allocation in compression and decompression

Increase decompression read buffer from 4KB to 32KB in gzip, brotli,
and brotli_stream modules to reduce syscall overhead during chunked
reads. Pre-allocate compression output Vecs with input.len() capacity
for gzip and deflate encoders (both sync and async) to avoid repeated
reallocations during compression.

Closes #100

* revert: keep BUFFER_SIZE at 4096 to avoid decompress regression

The 32KB buffer caused 21 benchmark regressions in CodSpeed due to
increased stack zeroing cost. Revert to 4KB. The Vec::with_capacity
optimization for compression output is retained.

* perf(core): replace Vec clone and split_off with std::mem::take in streaming contexts (#112)

Use std::mem::take() instead of .get_ref().clone() + .get_mut().clear()
in brotli streaming contexts, and instead of .split_off(0) in gzip/deflate
streaming contexts. This avoids unnecessary memory allocation and copying
by moving ownership of the buffer contents in-place.

Closes #99

* fix(zstd): add compression level validation (#113)

Validate that the compression level is within the valid zstd range
(-131072 to 22) before passing it to the zstd library. This provides
clear error messages instead of opaque library errors.

Validation is added to:
- zstd_compress (sync)
- zstd_compress_async
- ZstdCompressContext::new (streaming)
- zstd_compress_with_dict

Closes #81

* feat(zstd): add streaming dictionary compression/decompression (#114)

Add ZstdCompressDictContext and ZstdDecompressDictContext classes that
accept a pre-trained dictionary for streaming compression/decompression.
Include Web Streams (createZstdCompressDictStream/createZstdDecompressDictStream)
and Node.js Transform (createZstdCompressDictTransform/createZstdDecompressDictTransform)
wrappers, type definitions, ESM exports, and streaming dict round-trip tests.

Closes #104

* perf(core): switch flate2 backend from miniz_oxide to zlib-rs (#115)

Replace the `rust_backend` (miniz_oxide) feature with `zlib-rs` for
2-3x faster gzip/deflate compression. zlib-rs is a pure-Rust
implementation that works on all targets including WASM, is ISRG
audited, and is on track to become flate2's default backend.

Relax compression level size ordering test since zlib-rs may produce
slightly different output sizes per level than miniz_oxide — round-trip
correctness is what matters.

Closes #98

* perf(zstd): enable multi-threaded compression for native targets (#116)

Add `zstdmt` feature for non-WASM targets via conditional dependency.
This enables zstd's internal thread pool for compression, improving
performance by 20-80% for large inputs on multi-core systems.

WASM targets are excluded because wasi-threads is experimental and
the interaction with napi-rs worker-based threading is untested.

Closes #102

* docs: comprehensive README update with missing APIs and SVG benchmark (#118)

* fix(docs): correct benchmark environment label to Apple M2 / Node.js v22 (#119)

* perf(wasm): add --converge flag to wasm-opt optimization (#122)

Re-run optimization passes until no further gains are achieved.
Increase timeout to 5 minutes to accommodate additional passes.

Closes #121

* perf(core): use 4x input size for decompression output pre-allocation (#123)

Compressed data typically decompresses to several times its original
size. Pre-allocating with input.len() alone leads to frequent
reallocations during decompression. Using input.len() * 4 as the
initial capacity provides a better heuristic while the existing
.min() caps continue to prevent excessive allocation.

Closes #120

* ci: migrate release workflow to npm trusted publishing (OIDC) (#68)

* ci: migrate release workflow to npm trusted publishing (OIDC)

Remove NPM_TOKEN/NODE_AUTH_TOKEN secret dependency and rely on
OIDC token exchange via id-token:write permission (already set).
Trusted publishing must be configured on npmjs.com for each package
before the next release.

* fix(ci): restore pinned SHA for dtolnay/rust-toolchain

Keep the pinned commit hash consistent with ci.yml and codspeed.yml
for supply chain security. The rust-toolchain change was out of scope
for the trusted publishing migration.

* fix(gzip): support concatenated gzip streams in decompression (#131)

Replace GzDecoder with MultiGzDecoder from flate2 to correctly handle
concatenated gzip members in both one-shot and streaming decompression.

* fix(streaming): add finish-guard state tracking for brotli and zstd compress contexts (#132)

* feat(core): add missing async variants for one-shot APIs (#133)

* feat(streaming): add auto-detect decompression streams (#145)

* docs: add troubleshooting section to CONTRIBUTING.md (#146)

* docs: add CODE_OF_CONDUCT.md (#149)

* ci: apply least-privilege permissions to workflows (#147)

* chore: improve package.json metadata and exports map (#148)

* ci: clean up CI workflow configuration (#150)

* feat(core): expose CRC32 utility function (#134)

* feat(core): expose CRC32 utility function

* test(crc32): use skipIf for conditional Node.js zlib interop test

* ci(testing): add coverage thresholds to vitest config (#151)

* ci: add aggregate gate job to CI workflow (#152)

* feat(streaming): add configurable maxOutputSize to decompression contexts (#153)

Add optional maxOutputSize parameter to all streaming decompression
context constructors (GzipDecompressContext, DeflateDecompressContext,
ZstdDecompressContext, BrotliDecompressContext, ZstdDecompressDictContext).
When not specified, the default 256 MB limit is used.

The parameter is also passed through all streaming factory functions
(createXxxDecompressStream, createXxxDecompressTransform, and
createDecompressStream/createDecompressTransform).

Closes #129

* ci: add environment protection for npm publish (#154)

* ci: add cargo-shear for unused dependency detection (#155)

* ci: add WASM binary size tracking on pull requests (#156)

* ci: add scripts/ to CI workflow path triggers (#159)

* fix(wasm): add JS-side streaming adapters for browser WASM environment (#158)

Replace native streaming contexts with JS-side adapters in the browser
entry point to work around WebAssembly.Memory growth invalidating
ArrayBuffer views. Adapters accumulate chunks and delegate to one-shot
APIs, maintaining the same transform()/flush()/finish() API surface.

- browser-streaming.js: adapter classes for all 10 streaming contexts
- browser-entry.js: re-exports WASM APIs with adapter overrides
- package.json: browser condition points to browser-entry.js
- e2e: streaming tests enabled for browser WASM

* feat(gzip): add header metadata support for compression and reading (#160)

Closes #130

* chore(renovate): run daily and remove PR concurrent limit (#164)

* chore(renovate): run daily and remove PR concurrent limit

Closes #163

* chore(renovate): disable lock file maintenance

* chore(deps): update codspeedhq/action digest to 1c8ae48 (#161)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dtolnay/rust-toolchain digest to 631a55b (#162)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to 3869755 (#165)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to b988c18 (#166)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(bench): exclude third-party benchmarks from CodSpeed tracking (#169)

* fix(bench): exclude third-party benchmarks from CodSpeed tracking

Split benchmark files into zflate-only (*.bench.ts) and comparison
(*.compare.bench.ts) variants. CodSpeed now only tracks zflate's own
benchmarks, eliminating false regressions from pako/fflate/node:zlib
that are caused by GitHub Actions CPU heterogeneity affecting Callgrind
instruction counting.

Closes #167

* fix(bench): use deterministic random data in CodSpeed benchmarks

Replace non-deterministic randomBytes() with a seeded LCG to ensure
reproducible benchmark inputs across runs, reducing CodSpeed instruction
count variance.

* feat(bench): migrate from CodSpeed to Bencher.dev for regression detection (#170)

Replace CodSpeed simulation mode (Callgrind instruction counting) with
Bencher.dev (statistical wall-clock analysis). CodSpeed's instruction
counting was fundamentally incompatible with this napi-rs project due to
GitHub Actions CPU heterogeneity and glibc's CPU-adaptive malloc.

Changes:
- Add Bencher workflow with t-test threshold for regression detection
- Add vitest-to-bmf.js conversion script for vitest bench output
- Remove CodSpeed workflow and @codspeed/vitest-plugin dependency
- Remove CodSpeed plugin from vitest config

Closes #168

* feat(bench): switch to CodSpeed Walltime mode for accurate benchmarks (#172)

Replace Bencher.dev with CodSpeed Walltime mode (Macro Runners) which
runs benchmarks on dedicated bare-metal hardware, eliminating GitHub
Actions CPU variance. CodSpeed also provides native vitest integration
via @codspeed/vitest-plugin, removing the need for conversion scripts.

Key change: `mode: walltime` instead of the previous `mode: simulation`
which used Callgrind instruction counting.

Closes #171

* refactor(bench): split benchmark files per algorithm for granular CodSpeed reporting (#174)

Split index.bench.ts (mixed zstd+brotli) into separate zstd.bench.ts
and brotli.bench.ts so CodSpeed PR reports only show benchmarks relevant
to the changed algorithm. Extract shared test fixtures into
bench-fixtures.ts and add realistic data benchmarks (JSON, text) to
gzip and deflate for consistent coverage across all four algorithms.

Closes #173

* perf(core): eliminate double-copy buffer pattern in decompression (#178)

* perf(core): eliminate double-copy buffer pattern in decompression

Replace the manual 4 KiB read loop (stack buffer → Vec copy) with
read_to_end + Take, which writes directly into Vec spare capacity.
This eliminates one full copy of every decompressed byte and uses
adaptive buffer sizing instead of fixed 4 KiB chunks.

Affected: gzip, deflate, brotli (sync + async, default + with-capacity)

Closes #175

* fix(core): use saturating_mul for init capacity and update size limit tests

Address CodeRabbit review feedback:
- Replace len() * 4 with len().saturating_mul(4) across all call sites
  to prevent overflow on 32-bit targets (wasm32-wasip1-threads)
- Replace old chunk-loop size limit tests with Take+read_to_end tests
  that exercise the actual decompression pattern (avoiding napi::Error
  symbol resolution issues in standalone test binaries)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks (#179)

* chore(bench): add bench:ci script and Rust CodSpeed benchmarks

- Add bench:ci script that excludes comparison benchmarks
- Simplify CodSpeed workflow to use bench:ci instead of explicit file list
- Use glob pattern for bench file path triggers
- Add crates/bench workspace member with Criterion benchmarks
  (gzip, deflate, brotli, zstd) using codspeed-criterion-compat
- Add rust-benchmarks job to CodSpeed workflow (simulation mode)

Closes #177

* fix(ci): add Cargo.toml, Cargo.lock, package.json to CodSpeed path triggers

Ensures CodSpeed benchmarks run when workspace config or dependency
lockfiles change, which could affect benchmark behavior.

* feat(core): add LZ4 frame compression support (#180)

* feat(core): add LZ4 frame compression support

Add LZ4 as a fourth compression algorithm using lz4_flex (pure Rust):

- Bulk API: lz4Compress, lz4Decompress, lz4DecompressWithCapacity
- Async API: lz4CompressAsync, lz4DecompressAsync, lz4DecompressWithCapacityAsync
- Streaming: Lz4CompressContext, Lz4DecompressContext
- Web Streams: createLz4CompressStream, createLz4DecompressStream
- Format detection: detectFormat returns "lz4" for LZ4 frame magic bytes
- Auto-decompress: decompress/decompressAsync and createDecompressStream
  now handle LZ4 data automatically

Closes #176

* fix(core): add Read import to LZ4 test modules

Tests use read_to_end which requires the Read trait in scope.

* refactor(core): validate capacity as non-negative integer (#183)

* refactor(core): validate capacity as non-negative integer across all algorithms

Extract shared validate_capacity() that rejects NaN, Infinity, negative,
fractional values, and values exceeding usize::MAX. Applied to all 10
DecompressWithCapacity functions (sync + async × 5 algorithms).

Closes #181

* test: add regression tests for fractional and oversized capacity

* perf(lz4): implement incremental streaming for LZ4 compression (#184)

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with cursor-based incremental output in
Lz4CompressContext. The FrameEncoder now persists across transform()
calls, emitting compressed blocks as they become available instead of
buffering all input until finish().

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* perf(lz4): implement incremental streaming for LZ4 compression

Replace full-input buffering with FrameEncoder-based incremental output
in Lz4CompressContext. Compressed blocks are emitted as internal buffers
fill, and old bytes are drained to bound memory usage.

Decompression remains buffered due to lz4_flex FrameDecoder requiring
full input (documented in #182).

Closes #182

* chore(deps): update taiki-e/install-action digest to 0620367 (#185)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust crate lz4_flex to 0.13 (#186)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore: rename package from zflate to comprs (#190)

* chore: rename package from zflate to comprs

Rename npm package, Rust crates, napi binary name, platform packages,
and all references from "zflate" to "comprs". The name "comprs" conveys
"compress" with a Rust flavor, is available on npm, and avoids the
typosquat protection that blocks "zflate".

Changes across 70 files:
- npm: comprs, comprs-darwin-arm64, comprs-wasm32-wasi, etc.
- Rust: comprs (core), comprs-bench
- napi binaryName: comprs (→ comprs.*.node, comprs.*.wasm)
- README, workflows, changesets, tests, benchmarks

GitHub repository URL (derodero24/zflate) is preserved.

Closes #189

* chore: update GitHub repo URLs to derodero24/comprs

Update all repository references to match the planned GitHub repo
rename from derodero24/zflate to derodero24/comprs.

* chore(core): rename ZflateError to ComprsError and update bench vars (#192)

Rename internal Rust error type `ZflateError` → `ComprsError` across
all source files and update benchmark variable names `*_ZFLATE` →
`*_COMPRS` for consistency with the package rename in #190.

Closes #191

* fix: add missing LZ4 docs, browser exports, Node.js transforms, and publish files (#194)

- Add LZ4 one-shot and streaming context exports to browser-entry.js
- Add Lz4CompressContext/Lz4DecompressContext adapters to browser-streaming.js
- Add createLz4CompressTransform/createLz4DecompressTransform to node.js
- Add LZ4 type declarations to node.d.ts
- Add 'lz4' case to createDecompressContext in node.js
- Add browser-entry.js and browser-streaming.js to package.json files array
- Document LZ4 API in README.md (one-shot, async, streaming, comparison)
- Fix streaming import path in README.md (comprs → comprs/streams)
- Update auto-detect descriptions to include LZ4
- Add "lz4" to package.json keywords and description
- Add LZ4 changeset for upcoming release

Closes #193

* fix: add missing LZ4 exports to ESM entry point and complete API docs (#196)

- Add LZ4 one-shot APIs, Context classes, and streaming functions to
  index.mjs (ESM users could not access LZ4 at all)
- Add createLz4CompressStream/createLz4DecompressStream to index.mjs
- Add gzipCompressWithHeader and gzipReadHeader to browser-entry.js
- Document gzipCompressWithHeader, gzipReadHeader in README
- Document crc32 and version() utilities in README
- Document createDecompressStream and createDecompressTransform in README

Closes #195

* fix(docs): add missing lz4 to decompressAsync doc and clarify async section (#198)

The decompressAsync Rust doc comment listed only zstd, gzip, brotli as
supported formats, but the implementation also handles lz4. The README
async section claimed "all one-shot functions" have async variants, which
is inaccurate for utility functions like crc32, version, gzipReadHeader.

Closes #197

* chore(release): version packages (#35)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* perf(ci): remove JS walltime benchmarks from CodSpeed regression detection (#201)

JS walltime benchmarks on standard GitHub Actions runners produce 20-50%
variance due to CPU heterogeneity (AMD EPYC vs Intel Xeon assigned randomly)
and glibc's CPU-adaptive malloc. This caused CodSpeed Performance Analysis to
fail on nearly every PR since #174, including documentation-only changes.

Keep only Rust simulation benchmarks in CodSpeed, which use instruction
counting via Callgrind and produce <1% variance. Also narrow the workflow
path triggers to Rust-only files since JS changes no longer affect the
CodSpeed pipeline.

JS benchmarks remain available locally via `pnpm run bench`.

Closes #200

* ci(release): use granular access token for initial npm publish

The first publish requires an NPM_TOKEN since OIDC trusted publishing
only works for packages that already exist on the registry. After the
initial release, this will be switched back to OIDC.

* chore: add .mcp.json to .gitignore (#205)

Closes #203

* fix(core): use decompress_with_limit in async auto-detect decompression (#216)

Replace manual chunk-read loops with double-copy in DecompressTask for
gzip and brotli branches. Now uses crate::decompress_with_limit(),
matching the pattern already used by the LZ4 branch and per-algorithm
async tasks.

Closes #207

* ci: add browser files to CI path triggers (#217)

Closes #212

* fix(zstd): validate maxDictSize parameter in dictionary training (#219)

* test: update ESM smoke test to cover all exports (#215)

* Add LZ4 edge-case and streaming test coverage (#218)

* test(lz4): add edge-case and streaming test coverage

* test(lz4): fix empty buffer test to match actual lz4 behavior

LZ4 returns empty buffer for empty input (like zstd and deflate),
rather than throwing an error (like gzip and brotli).

* fix(zstd): enforce maxOutputSize in browser dict decompression (#221)

* fix(zstd): enforce maxOutputSize in browser dict decompression

Add zstd_decompress_with_dict_with_capacity Rust function and use it
in browser-streaming.js ZstdDecompressDictContext.flush() when
maxOutputSize is provided.

Closes #208

* chore: regenerate napi-rs binding files

* refactor(core): consolidate MAX_DECOMPRESSED_SIZE constant (#222)

Remove duplicated MAX_DECOMPRESSED_SIZE definitions from zstd, gzip,
brotli_impl, lz4, and detect modules. Change the lib.rs definition to
pub(crate) and reference it via crate::MAX_DECOMPRESSED_SIZE in all
modules.

* chore(release): version packages (#220)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* docs: add benchmark comparison results to README (#224)

Closes #213

* feat(brotli): add dictionary compression and decompression support (#225)

* docs: add brotli dictionary to README and update Node.js transforms (#228)

* docs: readme visual and structural overhaul (#239)

- Center-align hero section with title, tagline, and badges
- Add Node.js version and TypeScript badges
- Add Table of Contents for navigation
- Wrap benchmark tables in collapsible <details> sections
- Replace plain blockquote with GitHub Admonition (> [!TIP])
- Replace raw build target triples with human-readable table

Closes #229

* docs: reposition and enhance comparison table (#240)

- Move comparison table from bottom of README to right after "Why comprs?"
- Split "Streaming" into "Web Streams API" and "Node.js Transform" rows
- Replace ambiguous `✅*` with `⚠️ Experimental*` for node:zlib zstd
- Replace footnote `†` with inline "Chunked†" for pako streaming
- Update Table of Contents to reflect new section order
- Center-align feature columns for better scannability

Closes #236

* docs: improve quick start examples and add runtime-specific usage (#241)

- Condense one-shot examples to single primary zstd block + multi-algorithm block
- Replace impractical manual ReadableStream example with fetch-style streaming
- Add Node.js Transform stream example to Quick Start
- Add brief comment explaining async runs on libuv thread pool
- Consolidate compression levels into compact format
- Add Deno and Bun import examples

Closes #235

* docs: add algorithm selection guide (#242)

- Add "Choosing an Algorithm" section with use case → recommendation table
- Add "Choosing an API mode" subsection for sync/async/streaming/dictionary
- Update Table of Contents

Closes #234

* docs: document browser WASM requirements (SharedArrayBuffer, COOP/COEP) (#243)

- Add SharedArrayBuffer requirement warning with required HTTP headers
- Remove misleading "or import directly from a CDN" claim
- Link to MDN SharedArrayBuffer security requirements

Closes #231

* docs: add SSR framework integration guide (#244)

- Add "Framework Integration (SSR)" subsection under Browser Usage
- Include Next.js serverExternalPackages and Vite SSR external config
- Note automatic WASM fallback on client side

Closes #232

* docs: add fflate migration guide (#245)

- Add "From fflate" migration section with diff example
- Note comprs advantages over fflate (zstd, lz4, brotli, dictionaries, Web Streams)

Closes #237

* docs: add known limitations and performance notes (#246)

- Add "Notes" section with three admonitions covering:
  - 256 MB default decompression limit with WithCapacity() workaround
  - Small payload WASM overhead advisory
  - Brotli decompression performance nuance vs node:zlib

Closes #238

* docs: add all undocumented async and capacity API variants to readme (#247)

- Add zstdDecompressWithDictWithCapacity to sync zstd dictionary table
- Add 9 missing async variants: WithCapacityAsync for all algorithms,
  zstd dict async, zstdTrainDictionaryAsync, decompressAsync
- Reorder async table to group by algorithm with capacity/dict variants

Closes #233

* docs: add benchmark comparison SVG charts for cross-algorithm and brotli (#248)

- Add cross-algorithm compression chart (zstd/lz4/gzip/brotli at 10KB)
- Add brotli compression speedup chart (134.7x faster than node:zlib)
- Embed both charts alongside existing gzip chart in Benchmarks section

Closes #230

* docs: fold repetitive API tables into collapsible sections (#250)

- Wrap Dictionary API (zstd + brotli) in <details>
- Replace Async table header with pattern explanation + code example,
  fold full function list into <details>
- Add Streaming import path + fold table into <details>
- Keep Node.js Transform code example visible, fold table into <details>
- One-shot core API tables remain visible as primary reference

Closes #249

* chore(deps): update taiki-e/install-action digest to 7627fb4 (#251)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.9 (#252)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.6 (#254)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.3 (#253)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update pnpm to v10.33.0 (#256)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.1 (#255)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency typescript to v6 (#257)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* feat: add interactive playground with GitHub Pages deployment (#259)

Add a browser-based playground for trying comprs compression live.
Includes four algorithms (zstd, gzip, brotli, lz4), level slider,
comparison table, file upload, and download. Deploys to GitHub Pages
via new CI workflow. Uses a JS mock for local dev without a WASM build.

Closes #258

* docs: improve playground visibility and add OGP/SEO meta tags (#261)

Add prominent text link below badges in README header and playground
as a bullet in "Why comprs?". Add Open Graph, Twitter Card, and
canonical URL meta tags to playground/index.html for social sharing.

Closes #260

* fix(playground): handle COI not established on first visit (#263)

On first visit, the COI service worker has not yet reloaded the page,
so crossOriginIsolated is false. Calling the WASM function immediately
throws because SharedArrayBuffer is unavailable. This showed a
misleading "unsupported browser" error even in modern browsers like
Vivaldi, Brave, or Arc.

- Check crossOriginIsolated before invoking WASM
- Show "Enabling security features... reloading" while the service
  worker handles the reload
- After 3 s, show a manual Reload button if auto-reload did not happen
- Include the actual error message when WASM init itself fails
- Add .loading-reload-btn styles to style.css

Closes #262

* fix(playground): add Buffer polyfill for emnapi WASM compatibility (#265)

The napi-rs WASM runtime (emnapi) requires globalThis.Buffer when
creating memory views for function arguments/return values. Browsers
do not define Buffer natively, causing zstdCompress and other WASM
calls to throw immediately.

Add the 'buffer' npm package (v6 browser polyfill) to playground
dependencies and set globalThis.Buffer before any WASM calls.

Closes #264

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init (#267)

* fix(playground): use dynamic import for comprs to ensure Buffer is set before WASM init

Static imports are hoisted and evaluated before any module body code runs.
This caused globalThis.Buffer to be unset when comprs-wasm32-wasi evaluated
__emnapiInstantiateNapiModuleSync (which calls emnapi_create_memory_view).

Switch to a dynamic import so the Buffer polyfill assignment executes first.

Closes #266

* fix(playground): allow Vite dev server to serve WASM from parent directory

When comprs.wasi-browser.js is present in the repo root, the WASM file
is fetched at runtime via /@fs/ from outside the playground directory.
Add server.fs.allow so Vite does not block the request with 403.
Only applied when a local WASM build is detected (hasLocalWasm=true).

* fix(playground): fix comparison table label direction and badge layout (#271)

- Rename bar chart column 'Ratio (smaller = better)' to 'Savings'
  and numeric column 'Ratio' to 'Saved' to accurately reflect that
  the metric is compression savings % (larger = better)
- Change 'best ratio' badge to 'best' (shorter, clearer)
- Add 'fastest' badge to the fastest algorithm row; remove the
  duplicate 'Fastest:' text from the footer note
- Change badge column from fixed 72px to auto to prevent overflow

Closes #270

* fix(playground): fix negative ratio text, CSS variable, download label, and file size alert (#273)

- Show "X% larger" instead of "-X% smaller" when compressed output exceeds input
- Fix reload button using undefined var(--accent); use var(--text)/var(--bg) instead
- Add file extension to download button (e.g. "Download .zst") via #download-ext span
- Replace alert() for oversized file with inline error message that auto-dismisses after 5s

Closes #272

* chore(deps): update codecov/codecov-action digest to 75cd116 (#268)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.2 (#269)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(playground): add FileReader error handler and textarea aria-label (#275)

- Add reader.onerror handler to show inline error when file reading fails
  instead of silently doing nothing (e.g. on disk error or revoked access)
- Add aria-label to the input textarea for screen reader accessibility

Closes #274

* chore(deps): update dtolnay/rust-toolchain digest to 29eef33 (#276)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to c10b806 (#277)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/configure-pages action to v6 (#283)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/deploy-pages action to v5 (#284)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-pages-artifact action to v4 (#285)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to d858f81 (#278)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update rust-dependencies to v3.8.4 (#281)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.2 (#280)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.10 (#279)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update codecov/codecov-action action to v6 (#286)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2 (#287)

* chore(deps): update @napi-rs/cli to v3.6.0 and @emnapi/* to v1.9.2

Update @napi-rs/cli from v3.5.1 to v3.6.0 along with @emnapi/core
and @emnapi/runtime from v1.9.1 to v1.9.2 to resolve emnapi version
mismatch that caused WASM build failures.

* fix(deps): align emnapi transitive dependency to v1.9.2

Update the emnapi package (transitive via @napi-rs/cli) from v1.9.1
to v1.9.2, matching @emnapi/core and @emnapi/runtime versions.

* chore(deps): update renovatebot/github-action action to v46.1.7 (#292)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @types/node to v24.12.2 (#291)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(config): update biome.json schema to v2.4.10 (#294)

* chore(deps): update dependency @playwright/test to v1.59.1 (#293)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* fix(core): remove unused wildcard import in crc test module (#295)

* fix(ci): update setup-deno and changesets/action to resolve Renovate lookup failures (#296)

Pin to specific version tags instead of major version floating tags,
which do not exist in these repositories. This allows Renovate to
resolve digests for tracking updates.

- denoland/setup-deno: v2 → v2.0.4
- changesets/action: v1 → v1.7.0

Closes #290

* chore(release): version packages to v0.4.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: rename npm package to @derodero24/comprs (#298)

Rename all npm packages from `comprs-*` to `@derodero24/comprs-*` to
bypass npm's typosquatting protection that blocked the `comprs` name.

- Root package: comprs → @derodero24/comprs
- 9 platform packages: comprs-{platform} → @derodero24/comprs-{platform}
- Regenerated napi-rs loader (index.js, browser.js) with new names
- Updated release workflow version checks
- Updated README install/import examples
- Updated playground npm link

Closes #297

* fix(playground): update vite alias for renamed WASM package

Update the vite resolve alias from 'comprs-wasm32-wasi' to
'@derodero24/comprs-wasm32-wasi' to match the package rename in #298.

* fix(ci): add publishConfig and permissions for scoped npm publish (#302)

- Add publishConfig.access: "public" to root and all platform
  package.json files so napi prepublish can publish scoped packages
- Add contents: write permission to Publish job for GitHub Release
  creation by napi prepublish
- Add changeset for v0.4.1 patch release

Closes #301

* chore(release): version packages to v0.4.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: update stale package references after rename to @derodero24/comprs (#307)

Update remaining references from old unscoped names to scoped names:

- browser-streaming.js: comprs-wasm32-wasi → @derodero24/comprs-wasm32-wasi
- playground/main.js: import('comprs') → import('@derodero24/comprs')
- playground/package.json: comprs → @derodero24/comprs dependency
- playground/vite.config.js: Vite alias keys updated

This fixes the GitHub Pages deployment failure caused by Vite being
unable to resolve the old unscoped import in browser-streaming.js.

Closes #306

* fix(playground): regenerate lockfile after package rename

The playground's pnpm-lock.yaml was not updated when the dependency
was renamed from comprs to @derodero24/comprs, causing CI to fail
with frozen-lockfile mismatch.

* docs: fix README package references, streaming note, and WASM size (#311)

* fix(docs): update remaining old package name references in README

- Deno import: npm:comprs → npm:@derodero24/comprs
- Subpath imports: comprs/streams → @derodero24/comprs/streams,
  comprs/node → @derodero24/comprs/node
- SSR config: Next.js and Vite external package names

Closes #308

* docs: clarify browser streaming memory behavior

Add note explaining that browser WASM streaming buffers all data in
memory (unlike Node.js which streams incrementally). Also qualify the
"bounded memory" claim in the feature list to specify Node.js.

Closes #309

* docs: add concrete WASM binary size to README

Show approximate optimized WASM size (~2.0 MB raw, ~800 KB gzipped)
directly in README so users can evaluate without navigating to CI.

Closes #310

* fix: update npm badges and sub-package descriptions (#317)

* fix(docs): update npm badges to use scoped package name

The npm version and downloads badges were pointing to the old unscoped
package name "comprs" instead of the scoped "@derodero24/comprs".

Closes #312

* fix: update platform sub-package descriptions to include lz4

All 9 platform sub-packages under npm/ had descriptions listing only
"zstd, gzip, and brotli" but were missing "lz4" which was added to
the root package. Updated to match: "zstd, gzip, brotli, and lz4".

Closes #315

* fix: add missing ESM export and test coverage (#318)

* fix: add missing zstdDecompressWithDictWithCapacity to ESM and browser exports

Add zstdDecompressWithDictWithCapacity to index.mjs destructured exports,
browser-entry.js one-shot re-exports, and ESM smoke test assertions.

Closes #313

* test: add coverage for WithDictWithCapacity functions and Node.js Transform streams

Add tests for zstdDecompressWithDictWithCapacity, brotliDecompressWithDictWithCapacity,
brotliDecompressWithDictWithCapacityAsync, LZ4 Node.js Transform round-trip,
zstd dict Node.js Transform round-trip, and brotli dict Node.js Transform round-trip.

Closes #314

* feat(wasm): replace WASI WASM with wasm-bindgen for browser compatibility (#320)

* refactor(core): extract pure Rust logic into core-lib crate

Split crates/core into two crates:
- crates/core-lib: pure Rust compression logic (no FFI dependencies)
- crates/core: napi-rs thin wrappers delegating to core-lib

This prepares for adding a wasm-bindgen crate that shares the same
core logic, enabling browser usage without SharedArrayBuffer.

Part of #319

* feat(wasm): add wasm-bindgen browser binding crate

Create crates/wasm with wasm-bindgen wrappers over comprs-core,
targeting wasm32-unknown-unknown (no SharedArrayBuffer required).

Includes one-shot and streaming APIs for all algorithms:
zstd, gzip, deflate, brotli, lz4, auto-detect, and crc32.

Part of #319

* feat(wasm): integrate wasm-bindgen build into package distribution

- Add build:wasm-bindgen script using wasm-pack
- Update browser.js to use wasm-bindgen output
- Update browser-streaming.js imports
- Add wasm-bindgen files to npm package
- Update CI with wasm-bindgen build job
- Update release workflow for wasm-bindgen artifacts
- Update playground configuration
- Remove SharedArrayBuffer requirement from README

Part of #319

* fix(ci): resolve CI failures for wasm-bindgen migration

- Remove browser.js from napi generated file diff check (napi build
  regenerates it, but we now maintain it manually for wasm-bindgen)
- Remove unused direct compression deps from napi crate (all logic
  now lives in core-lib)
- Update e2e browser test to use wasm-bindgen output instead of
  WASI browser entry
- Remove COOP/COEP headers from e2e vite config (no longer needed)

Part of #319

* fix(e2e): use manual WASM instantiation for wasm-bindgen in browser test

Vite 8 does not support the TC39 WebAssembly ESM integration proposal.
Import _bg.js directly and instantiate WASM binary manually, following
the same pattern used in rapid-fuzzy.

Also add lz4, crc32, and detectFormat tests to the browser e2e suite.

Part of #319

* fix(e2e): update browser test spec to match wasm-bindgen test suite

Replace streaming tests (removed with WASI migration) with lz4,
crc32, and detectFormat assertions.

Part of #319

* chore: add changeset for v1.0.0 major release (#322)

Closes #321

* chore(release): version packages to v1.0.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: address post-release bugs in v1.0.0 (#327)

- Add missing zstdDecompressWithDictWithCapacityAsync to index.mjs
- Sync crates/core-lib and crates/wasm version to 1.0.0
- Fix sync-cargo-version.js to cover all three Cargo.toml files
- Add missing async functions to README API list
- Add missing ESM smoke test assertion

Closes #326

* chore: add changeset for v1.0.1 patch release (#328)

Covers the bug fixes from #327:
- Missing zstdDecompressWithDictWithCapacityAsync ESM export
- Version mismatch in core-lib and wasm Cargo.toml
- Missing async functions in README API list

* chore(release): version packages to v1.0.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(playground): fix GitHub Pages deployment with wasm-bindgen (#333)

Root cause: napi build/version overwrites browser.js to point at WASI
WASM, and Vite 8 rejects ESM WASM imports from wasm-pack bundler output.

Fixes:
- Add scripts/patch-browser-js.js to restore browser.js after napi
  overwrites it (runs in changeset:version and build:wasm-bindgen)
- Add playground/wasm-loader.js for manual WASM instantiation
  (Vite 8 ESM WASM workaround, same pattern as rapid-fuzzy)
- Update playground vite.config.js to use wasm-loader
- Remove obsolete Buffer polyfill from playground/main.js

Closes #332

* chore: improve npm discoverability and update CONTRIBUTING.md (#334)

- Add keywords: compress, wasm-bindgen, native, web-streams
- Update CONTRIBUTING.md project structure for three-crate architecture
- Fix dead "GitHub Discussion" reference (Discussions is disabled)

* perf: v1.1.0 performance improvements (#348)

* perf(gzip): use ISIZE field for decompression buffer pre-allocation

Read the gzip footer's ISIZE field (last 4 bytes, little-endian uint32)
to pre-allocate the exact output buffer size instead of using a fixed
4x heuristic. This eliminates Vec re-allocations for most decompression
workloads.

Falls back to 4x heuristic when ISIZE is 0 (empty data or >4GB wrapping)
or when input is too small to be valid gzip.

Closes #335

* perf(zstd): enable multi-threaded compression (zstdmt)

Add zstdmt feature flag to comprs-core, enabled by default in the
napi crate (Node.js) but not in the wasm crate (single-threaded).

This allows zstd to use multiple threads for compression of large data,
providing 2-4x speedup on multi-core systems.

Closes #336

* perf(streaming): reuse internal buffers instead of allocating per transform

Add a reusable output_buf field to all zstd streaming context structs.
Vec::resize reuses existing capacity after the first call, eliminating
the 128KB allocation + zeroing overhead on subsequent transform calls.

The gzip, brotli, and lz4 streaming contexts delegate buffering to
their underlying encoder/decoder inner Vec<u8> (drained via mem::take),
so they do not have the same per-call allocation pattern and are
unaffected.

Closes #337

* perf(zstd): add comparison benchmark against node:zlib

Add zstd.compare.bench.ts comparing comprs zstd performance against
Node.js 22+ built-in zlib.zstdCompressSync/zstdDecompressSync.

Closes #338

* perf(streaming): add Transform stream throughput benchmarks

Add streaming.compare.bench.ts measuring Node.js Transform stream
throughput for comprs vs node:zlib, with various chunk sizes.

Closes #340

* chore: add changeset for v1.1.0 performance improvements (#349)

Covers #335 (gzip ISIZE), #336 (zstdmt), #337 (streaming buffer reuse).

* chore(release): version packages to v1.1.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore(deps): update taiki-e/install-action digest to 7a562df (#353)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update embarkstudios/cargo-deny-action digest to 175dc7f (#354)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to 97a5807 (#355)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update renovatebot/github-action action to v46.1.8 (#361)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency lefthook to v2.1.5 (#359)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.11 (#356)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency vite to v8.0.8 (#360)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/cli to v3.6.1 (#357)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/wasm-runtime to v1.1.3 (#358)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update vitest monorepo to v4.1.4 (#362)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): set minimumReleaseAge to 3 days in Renovate (#363)

Prevent adopting newly released packages that may be yanked or have
undiscovered issues. This applies globally to npm, Cargo, and GitHub
Actions dependencies.

* feat(middleware): add Express HTTP compression middleware (#364)

* feat(middleware): add Express HTTP compression middleware

Add @derodero24/comprs-middleware — the first Express middleware with
zstd support. Includes Accept-Encoding negotiation, configurable
algorithm priority, threshold-based skip, and Content-Type filtering.

- Set up pnpm workspace for multi-package monorepo
- Implement negotiation, compression, and middleware modules
- Add 31 unit and integration tests
- Update CI to lint/test the middleware package
- Update release workflow for multi-package publishing

Closes #343

* fix(middleware): address CodeRabbit review feedback

- Make isCompressibleType return false for missing Content-Type
- Add stream error handler to prevent unhandled exceptions
- Remove wildcard fallback that could override explicit rejections
- Separate middleware publish job for independent releases
- Limit middleware CI tests to ubuntu/node24 only
- Narrow biome noBarrelFile override to middleware package
- Refactor test helper to support HEAD requests
- Add HEAD request skip test
- Remove "first" claim from README descriptions

* fix(middleware): fix description, docs, and add typescript devDep

- Remove "First" claim from package.json description
- Update filter JSDoc to reflect Content-Type skip behavior
- Add typescript to devDependencies for standalone builds
- Fix filter default description in README table

* fix(middleware): update lockfile for typescript devDependency

* fix(middleware): normalize write/end args and preserve callbacks

Properly handle all overload forms of res.write() and res.end():
- Normalize chunk, encoding, and callback from variadic args
- Pass callbacks through to compressStream.write/end
- Handle res.end(callback) without treating function as chunk body
- Use null-check (!=) instead of truthy for chunk to handle empty strings

* chore(release): version packages (#365)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): check all workspace packages for unpublished versions (#367)

* fix(ci): check all workspace packages for unpublished versions

The release PR creation step only checked the core package version,
preventing middleware-only releases from triggering a develop→main PR.

Now checks both @derodero24/comprs and @derodero24/comprs-middleware
versions against npm, creating a release PR if any package is unpublished.

Closes #366

* fix(ci): read package names from manifest and handle npm errors

- Read package names from package.json instead of hardcoding
- Distinguish E404 (unpublished) from network/auth errors
- Fail the job on unexpected npm view errors instead of silently skipping

* feat(middleware): add Fastify and Hono support via subpath exports (#370)

* feat(middleware): add Fastify and Hono support via subpath exports

Restructure @derodero24/comprs-middleware from Express-only to
multi-framework with subpath exports:

- ./express — Express/Connect middleware (refactored from root export)
- ./fastify — Fastify plugin (onSend hook with stream compression)
- ./hono    — Hono middleware (sync compression via Web Standards API)
- .         — shared utilities (negotiate, types)

Extract framework-agnostic logic into shared.ts. Add 21 new tests
for Fastify (10) and Hono (11), total 53 tests across 4 test files.

Closes #369

* fix(middleware): address CodeRabbit review feedback

- Handle Vary: * without appending Accept-Encoding (RFC 7231)
- Extract shouldSkip helper in Hono adapter to reduce complexity
- Add Content-Length early threshold check in Hono to avoid body read
- Log compression pipeline errors in Fastify (skip premature close)
- Add stream compression behavior comment in Fastify
- Add identity-only Accept-Encoding test for Fastify
- Fix README import examples to use separate blocks per framework

* fix(middleware): case-insensitive Cache-Control check, Hono error handling

- Normalize Cache-Control to lowercase before checking no-transform (all adapters)
- Set Vary header before HEAD early return in Hono
- Wrap Hono body read + compression in try/catch for graceful fallback

* fix(ci): use manifest name and E404 check in publish-middleware job (#373)

Read package name from package.json instead of hardcoding.
Distinguish E404 (unpublished) from network/auth errors to prevent
silent failures during middleware publishing.

Closes #372

* chore(release): version packages (#371)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): add post-release merge-back from main to develop (#375)

After each release, merge main back into develop to prevent history
divergence. Without this, squash-merged release commits on main
cause growing conflicts on the next develop→main release PR.

Closes #374

* chore(deps): update commitlint monorepo to v20.5.3 (#382)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @biomejs/biome to v2.4.13 (#381)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/upload-artifact digest to 043fb46 (#376)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update taiki-e/install-action digest to db5fb34 (#377)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update embarkstudios/cargo-deny-action digest to 91bf2b6 (#378)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update actions/setup-node digest to 48b55a0 (#380)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update github/codeql-action digest to e46ed2c (#379)

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore(deps): update dependency @napi-rs/cli to v3.6.2 (#383)

* chore(deps): update dependency @napi-rs/cli to v3.6.2

* fix(deps): bump @emnapi/core and @emnapi/runtime to ^1.10.0

@napi-rs/cli@3.6.2 pulls in emnapi@1.10.0 transitively but @emnapi/core
and @emnapi/runtime were pinned to ^1.9.2, causing a runtime version
mismatch error during 'napi build --target wasm32-wasip1-threads':

  Internal Error: emnapi version mismatch:
    emnapi@1.10.0, @emnapi/core@1.9.2, @emnapi/runtime@1.9.2

Bump both peers to ^1.10.0 so all three resolve to the same version.

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>

* chore!: drop Node.js 20 support and bump napi ABI to napi9 (#386)

Node.js 20 reached end-of-life on 2026-04-30. Raise the minimum supported
Node.js to 22 (Active LTS) and bump the napi-rs ABI feature from napi6 to
napi9 (Node 18.17+ / 20.3+), which is safe under the new floor.

- Remove Node 20 from CI test matrix
-…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement zstd compression and decompression

1 participant