Skip to content

feat: add binary-safe compression and decompression APIs and implemen… - #1

Open
henningpokriefke wants to merge 7 commits into
mainfrom
adaptura/binary-zstd-codec
Open

feat: add binary-safe compression and decompression APIs and implemen…#1
henningpokriefke wants to merge 7 commits into
mainfrom
adaptura/binary-zstd-codec

Conversation

@henningpokriefke

Copy link
Copy Markdown
Owner

…t codec adapters

@henningpokriefke henningpokriefke self-assigned this Jan 13, 2026
@coderabbitai

coderabbitai Bot commented Jan 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added binary-safe compressBytes/decompressBytes for ArrayBuffer data
    • Introduced text and binary compression adapters and codec interfaces for extensibility
    • Exposed codecs from the package and re-exported codec utilities from the main entry
    • Maintained backward compatibility with existing string-based API
  • Tests

    • Added extensive tests covering binary APIs, adapters, backward compatibility, fuzzing, and codecs

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds binary-safe ArrayBuffer compress/decompress APIs in C++ and JS, introduces codec interfaces and two adapters, exposes a new ./codecs package export, and adds extensive unit and fuzz tests validating string and binary interoperability.

Changes

Cohort / File(s) Summary
C++ Binary-Safe Methods
cpp/HybridZstd.cpp, cpp/HybridZstd.hpp
Add HybridZstd::compressBytes(std::shared_ptr<ArrayBuffer>, double) and HybridZstd::decompressBytes(std::shared_ptr<ArrayBuffer>) that operate on ArrayBuffer and return ArrayBuffer without string conversions; throw on allocation/decompression failures.
Public JS API & Nitro Types
src/index.tsx, src/Zstd.nitro.ts, package.json
Export compressBytes / decompressBytes from the JS entry; extend Nitro Zstd interface with binary methods; add ./codecs entry to package exports.
Codecs Types & Index
src/codecs/types.ts, src/codecs/index.ts
Add interfaces BlobCodecV1 and CompressionV1; export codec adapters and types from codec index.
Codec Implementations
src/codecs/ZstdBinaryCompression.ts, src/codecs/ZstdTextCompressionAdapter.ts
Add ZstdBinaryCompression (binary-safe, preserves ArrayBuffer view offsets) and ZstdTextCompressionAdapter (text-only via UTF‑8) implementing CompressionV1.
Tests — binary, codecs, fuzz, compatibility, index
src/__tests__/* (binary.test.ts, codecs.test.ts, fuzz.test.ts, backward-compat.test.ts, index.test.tsx, ...)
Add comprehensive unit and fuzz tests: binary and string round-trips, cross-API parity, adapter behavior, compression level handling, and export presence; include mocked Nitro/native implementations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant JSIndex as index.tsx
    participant Nitro as NitroModule
    participant NativeCPP as HybridZstd (C++)
    Client->>JSIndex: compressBytes(ArrayBuffer)
    JSIndex->>Nitro: call compressBytes(ArrayBuffer)
    Nitro->>NativeCPP: invoke HybridZstd::compressBytes
    NativeCPP->>NativeCPP: allocate output buffer and compress
    NativeCPP-->>Nitro: return compressed ArrayBuffer
    Nitro-->>JSIndex: compressed data
    JSIndex-->>Client: return ArrayBuffer
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding binary-safe compression/decompression APIs and codec adapters, which matches the primary focus of the changeset.
Description check ✅ Passed The description completes the truncated title by indicating implementation of codec adapters, relating to the primary changes in the pull request.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@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: 8

🤖 Fix all issues with AI agents
In @src/__tests__/backward-compat.test.ts:
- Around line 10-48: Duplicate mock implementation found for
NitroModules.createHybridObject across tests; extract the mock into a shared
helper and replace inline mocks with calls to it. Create a helper that exports
createMockHybridObject() (returning the
compress/decompress/compressBytes/decompressBytes implementations) and
mockNitroModules() (returning { NitroModules: { createHybridObject: () =>
createMockHybridObject() } }), then update test files to import mockNitroModules
and use jest.mock('react-native-nitro-modules', () => mockNitroModules())
instead of duplicating the implementation.

In @src/__tests__/binary.test.ts:
- Around line 1-43: The tests duplicate the same mock implementation for the
native module (functions compress, decompress, compressBytes, decompressBytes)
across multiple test files; extract that mock into a shared test utility or
fixture (e.g., a module export like createNitroMock or setupNitroMocks) and
update binary.test.ts, fuzz.test.ts, and backward-compat.test.ts to import and
use the shared mock setup so the header-prepending/prep removal behavior is
centralized and maintained in one place.

In @src/__tests__/codecs.test.ts:
- Around line 193-199: Add a test that verifies ZstdBinaryCompression correctly
round-trips an empty Uint8Array: create an empty Uint8Array, call the adapter's
compress and then decompress (use the ZstdBinaryCompression or
ZstdBinaryCompressionAdapter instance used in the suite, e.g., variable named
`binaryCodec`/`codec`), and assert the decompressed result is an empty
Uint8Array (or has length 0). Place this alongside the existing binary-safety
tests so empty-byte behavior is covered consistently with
ZstdTextCompressionAdapter.

In @src/__tests__/fuzz.test.ts:
- Around line 56-61: The test inconsistently uses the TypeScript assertion "as
ArrayBuffer" on generateRandomBytes().buffer; make it consistent by removing the
redundant "as ArrayBuffer" casts where the buffer is directly owned (e.g., in
the calls to compressBytes in the tests using generateRandomBytes), so change
calls using compressBytes(input.buffer as ArrayBuffer, ...) to
compressBytes(input.buffer, ...), and apply the same removal for any other
places referencing input.buffer (e.g., decompressBytes inputs) to keep
generateRandomBytes, input.buffer, compressBytes and decompressBytes usage
uniform and more readable.
- Around line 41-48: The test uses non-deterministic Math.random() in
generateRandomBytes which causes flaky tests; replace it with a seeded PRNG so
runs are reproducible (e.g., add an optional seed parameter to
generateRandomBytes and use a simple LCG or a test-seeding library like
seedrandom to generate bytes deterministically), update any tests that call
generateRandomBytes to pass a fixed seed, and ensure the PRNG state is local to
generateRandomBytes to avoid global side effects.

In @src/__tests__/index.test.tsx:
- Around line 23-52: Tests repeatedly perform dynamic imports of the same
module; consolidate by importing the module once in a shared setup (e.g., a
beforeAll) and reuse the exported symbols (compress, decompress, compressBytes,
decompressBytes, ZstdBinaryCompression, ZstdTextCompressionAdapter) in each it
block to avoid redundant imports and improve test efficiency.

In @src/codecs/ZstdTextCompressionAdapter.ts:
- Around line 18-23: The compress method in ZstdTextCompressionAdapter currently
decodes bytes with TextDecoder without the fatal option, which silently replaces
invalid UTF-8; update the decode to use new TextDecoder('utf-8', { fatal: true
}) and wrap the decode call in a try/catch so invalid UTF-8 throws a clear error
(include context like "ZstdTextCompressionAdapter.compress" and the provided
defaultLevel) before proceeding to call compress(text, level ??
this.defaultLevel) and returning the Uint8Array.

In @src/index.tsx:
- Around line 6-21: The parameter name is inconsistent between compress
(compressLevel) and compressBytes (compressionLevel); to fix, rename the
compressBytes parameter to compressLevel and update its internal call to
ZstdHybridObject.compressBytes(data, compressLevel) so the exported API matches
the existing naming; if you must preserve backward compatibility, instead add a
thin wrapper overload named compressBytes(data: ArrayBuffer, compressLevel:
number = 3) that forwards to the current implementation (or keep
compressionLevel and add an aliased function/composed wrapper named
compressBytesWithCompressLevel) so both parameter names work.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 81ef51c and 93feab1.

⛔ Files ignored due to path filters (13)
  • nitrogen/generated/android/kotlin/com/margelo/nitro/zstd/reactnativezstdOnLoad.kt is excluded by !**/generated/**
  • nitrogen/generated/android/reactnativezstd+autolinking.cmake is excluded by !**/generated/**
  • nitrogen/generated/android/reactnativezstd+autolinking.gradle is excluded by !**/generated/**
  • nitrogen/generated/android/reactnativezstdOnLoad.cpp is excluded by !**/generated/**
  • nitrogen/generated/android/reactnativezstdOnLoad.hpp is excluded by !**/generated/**
  • nitrogen/generated/ios/Zstd+autolinking.rb is excluded by !**/generated/**
  • nitrogen/generated/ios/Zstd-Swift-Cxx-Bridge.cpp is excluded by !**/generated/**
  • nitrogen/generated/ios/Zstd-Swift-Cxx-Bridge.hpp is excluded by !**/generated/**
  • nitrogen/generated/ios/Zstd-Swift-Cxx-Umbrella.hpp is excluded by !**/generated/**
  • nitrogen/generated/ios/ZstdAutolinking.mm is excluded by !**/generated/**
  • nitrogen/generated/ios/ZstdAutolinking.swift is excluded by !**/generated/**
  • nitrogen/generated/shared/c++/HybridZstdSpec.cpp is excluded by !**/generated/**
  • nitrogen/generated/shared/c++/HybridZstdSpec.hpp is excluded by !**/generated/**
📒 Files selected for processing (14)
  • cpp/HybridZstd.cpp
  • cpp/HybridZstd.hpp
  • package.json
  • src/Zstd.nitro.ts
  • src/__tests__/backward-compat.test.ts
  • src/__tests__/binary.test.ts
  • src/__tests__/codecs.test.ts
  • src/__tests__/fuzz.test.ts
  • src/__tests__/index.test.tsx
  • src/codecs/ZstdBinaryCompression.ts
  • src/codecs/ZstdTextCompressionAdapter.ts
  • src/codecs/index.ts
  • src/codecs/types.ts
  • src/index.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
src/codecs/types.ts (2)
src/codecs/index.ts (2)
  • BlobCodecV1 (2-2)
  • CompressionV1 (2-2)
src/index.tsx (2)
  • BlobCodecV1 (29-29)
  • CompressionV1 (29-29)
src/Zstd.nitro.ts (2)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
nitrogen/generated/shared/c++/HybridZstdSpec.hpp (4)
  • data (52-52)
  • data (53-53)
  • data (54-54)
  • data (55-55)
src/__tests__/binary.test.ts (3)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
cpp/HybridZstd.cpp (4)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
src/codecs/ZstdTextCompressionAdapter.ts (3)
src/index.tsx (3)
  • CompressionV1 (29-29)
  • compress (7-9)
  • decompress (11-13)
src/codecs/types.ts (1)
  • CompressionV1 (22-38)
src/codecs/ZstdBinaryCompression.ts (2)
  • compress (18-28)
  • decompress (30-40)
src/__tests__/codecs.test.ts (3)
src/codecs/types.ts (1)
  • CompressionV1 (22-38)
src/codecs/ZstdBinaryCompression.ts (1)
  • ZstdBinaryCompression (11-41)
src/codecs/ZstdTextCompressionAdapter.ts (1)
  • ZstdTextCompressionAdapter (11-34)
src/__tests__/fuzz.test.ts (3)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
cpp/HybridZstd.cpp (5)
  • result (47-47)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
src/__tests__/index.test.tsx (3)
src/codecs/ZstdBinaryCompression.ts (3)
  • compress (18-28)
  • decompress (30-40)
  • ZstdBinaryCompression (11-41)
src/codecs/ZstdTextCompressionAdapter.ts (3)
  • compress (18-23)
  • decompress (25-33)
  • ZstdTextCompressionAdapter (11-34)
src/index.tsx (6)
  • compress (7-9)
  • decompress (11-13)
  • compressBytes (16-21)
  • decompressBytes (23-25)
  • ZstdBinaryCompression (28-28)
  • ZstdTextCompressionAdapter (28-28)
src/__tests__/backward-compat.test.ts (1)
src/index.tsx (4)
  • compress (7-9)
  • decompress (11-13)
  • compressBytes (16-21)
  • decompressBytes (23-25)
src/codecs/ZstdBinaryCompression.ts (3)
src/codecs/index.ts (2)
  • ZstdBinaryCompression (6-6)
  • CompressionV1 (2-2)
src/index.tsx (4)
  • ZstdBinaryCompression (28-28)
  • CompressionV1 (29-29)
  • compressBytes (16-21)
  • decompressBytes (23-25)
src/codecs/types.ts (1)
  • CompressionV1 (22-38)
cpp/HybridZstd.hpp (1)
nitrogen/generated/shared/c++/HybridZstdSpec.hpp (4)
  • data (52-52)
  • data (53-53)
  • data (54-54)
  • data (55-55)
🪛 Clang (14.0.6)
cpp/HybridZstd.cpp

[warning] 54-54: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 55-55: 2 adjacent parameters of 'compressBytes' of convertible types are easily swapped by mistake

(bugprone-easily-swappable-parameters)


[note] 55-55: the first parameter in the range is 'data'

(clang)


[note] 56-56: the last parameter in the range is 'compressionLevel'

(clang)


[note] 56-56: 'const int &' and 'double' parameters accept and bind the same kind of values

(clang)


[note] 56-56: 'const int &' and 'double' may be implicitly converted: 'const int &' (as 'int') -> 'double', 'double' -> 'const int &' (as 'int')

(clang)


[warning] 60-60: variable 'compressedData' is not initialized

(cppcoreguidelines-init-variables)


[warning] 84-84: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 89-89: variable 'decompressedData' is not initialized

(cppcoreguidelines-init-variables)

cpp/HybridZstd.hpp

[warning] 12-12: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 13-13: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 16-16: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 17-17: use a trailing return type for this function

(modernize-use-trailing-return-type)

🔇 Additional comments (20)
package.json (1)

13-17: LGTM!

The new ./codecs export entry follows the established pattern and correctly maps source, types, and module paths for the new codec adapters.

cpp/HybridZstd.hpp (1)

15-17: LGTM!

The binary-safe API declarations correctly match the generated HybridZstdSpec.hpp signatures and maintain consistency with the existing string-based API pattern.

src/__tests__/index.test.tsx (1)

11-21: LGTM!

The mock correctly provides stub implementations for all four native methods, enabling unit tests to verify export presence without requiring the native module.

src/Zstd.nitro.ts (1)

8-10: LGTM!

The binary-safe API additions are well-designed with clear naming (compressBytes/decompressBytes) and appropriate ArrayBuffer types that mirror the C++ implementation.

cpp/HybridZstd.cpp (2)

53-81: LGTM!

The compressBytes implementation correctly mirrors the string-based compress function with proper memory management. The null checks and cleanup on allocation failure are appropriate.


83-109: No bug found in the TypeScript adapter—both compress and decompress use identical, correct buffer slicing.

The C++ implementation is correct. However, the claimed TypeScript bug does not exist. Both the compress (lines 21–24) and decompress (lines 33–36) methods in ZstdBinaryCompression.ts use the same pattern: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), which correctly handles typed array views with non-zero offsets. There is no inconsistency.

Likely an incorrect or invalid review comment.

src/codecs/index.ts (1)

1-6: LGTM!

Clean barrel export with proper separation of type exports and value exports. The organization is clear and follows TypeScript best practices.

src/codecs/types.ts (1)

1-38: LGTM!

Well-designed interfaces with clear separation of concerns:

  • BlobCodecV1 for serialization (objects ↔ bytes)
  • CompressionV1 for compression (bytes ↔ compressed bytes)

The versioned naming convention (V1) provides a good foundation for future API evolution without breaking changes. Documentation is thorough with appropriate @throws annotations.

src/codecs/ZstdTextCompressionAdapter.ts (1)

25-33: LGTM!

The decompress method correctly handles Uint8Array views by properly slicing the underlying buffer with byteOffset and byteLength. This ensures correct behavior when the input is a view into a larger buffer.

src/__tests__/codecs.test.ts (4)

11-44: LGTM!

The mock implementation is well-designed, simulating ZSTD compression by prepending a header ("ZSTD" magic bytes) and preserving the payload. This allows round-trip testing without requiring the actual native module.


73-97: Excellent binary safety coverage.

The tests properly verify that null bytes, high-value bytes (0xff), and the full byte range (0-255) round-trip correctly. This is critical validation for the binary-safe API claims.


123-137: LGTM!

Good coverage of the edge case where Uint8Array is a view into a larger buffer with a non-zero offset. This validates that the implementations correctly use byteOffset and byteLength rather than assuming the view starts at the beginning of the underlying buffer.


220-246: LGTM!

Good adapter parity tests ensuring both implementations conform to the same interface and produce equivalent results for text data. This validates the interoperability story.

src/index.tsx (1)

27-29: LGTM!

Clean re-exports of the codec module, making adapters and types accessible from the main entry point. The separation of value exports from type exports is correct.

src/__tests__/binary.test.ts (1)

45-138: Comprehensive test coverage for binary compression API.

The test suite effectively covers:

  • Round-trip integrity for various byte patterns including null bytes (0x00) and high bytes (0xFF)
  • Full byte-range coverage (0x00-0xFF) ensuring all byte values are preserved
  • API contract validation (return types)
  • Compression level boundaries (1 and 19)

This provides good confidence that the binary API correctly handles arbitrary byte sequences.

src/__tests__/fuzz.test.ts (1)

64-122: Good coverage of edge cases and specific patterns.

The tests effectively cover:

  • Multiple iterations with varying random sizes
  • Power-of-2 boundary sizes (important for buffer allocation)
  • Repeating patterns (exercises compression dictionary)
  • Incrementing sequences
  • Mixed null/random bytes (validates binary safety)
src/codecs/ZstdBinaryCompression.ts (2)

18-28: Correct handling of Uint8Array views.

The implementation correctly uses bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) to handle cases where the input Uint8Array is a view into a larger ArrayBuffer. This ensures only the relevant portion is processed, which is critical for binary safety.

The as ArrayBuffer cast on line 24 is safe because ArrayBuffer.prototype.slice always returns an ArrayBuffer.


30-40: Consistent implementation matching the compress method.

The decompress method follows the same pattern for handling Uint8Array views. The absence of try-catch is appropriate here since the CompressionV1 interface documents that decompress throws on failure, delegating error handling to the caller.

src/__tests__/backward-compat.test.ts (2)

50-102: Thorough backward compatibility coverage for string API.

The test suite properly validates that the existing string-based API remains unchanged:

  • Input/output types preserved
  • Round-trip integrity for various text inputs including UTF-8
  • Default compression level behavior

This ensures the new binary API addition doesn't break existing consumers.


124-154: Good interoperability tests with appropriate mock caveats.

The interoperability tests verify practical use cases:

  1. Both APIs produce ArrayBuffer outputs
  2. Binary-compressed text can be decoded back to string after decompression

The comment on lines 136-137 correctly notes this equality test relies on mock behavior. In production, the native string API may handle encoding differently, so this byte-equality assertion is appropriate only for verifying mock consistency.

Comment thread src/__tests__/backward-compat.test.ts
Comment thread src/__tests__/binary.test.ts
Comment thread src/__tests__/codecs.test.ts
Comment thread src/__tests__/fuzz.test.ts
Comment thread src/__tests__/fuzz.test.ts
Comment thread src/__tests__/index.test.tsx
Comment thread src/codecs/ZstdTextCompressionAdapter.ts
Comment thread src/index.tsx
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@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: 2

🤖 Fix all issues with AI agents
In @src/__tests__/fuzz.test.ts:
- Around line 121-130: The test 'handles mixed null and random bytes' uses
Math.random() twice causing non-deterministic behavior; replace both
Math.random() calls with a seeded PRNG so the generated Uint8Array is
reproducible across runs (keep the same distribution: ~50% null vs random byte).
Update the loop in the test to initialize a deterministic RNG (or use an
imported seeded utility) and call it for both the null-vs-random decision and
the byte value generation, leaving calls to compressBytes and decompressBytes
unchanged and asserting equality as before.
- Around line 74-82: The test uses Math.random() to pick iteration sizes, making
the fuzzing non-deterministic; replace Math.random() with the file's seeded PRNG
function (the seeded generator defined earlier in this test file) so sizes are
reproducible across runs — e.g. call the existing seeded generator instead of
Math.random() when computing size in the loop that creates input via
generateRandomBytes, keeping the rest of the test that calls compressBytes and
decompressBytes unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 93feab1 and 11f9ac4.

📒 Files selected for processing (1)
  • src/__tests__/fuzz.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/__tests__/fuzz.test.ts (3)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
cpp/HybridZstd.cpp (4)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
🔇 Additional comments (4)
src/__tests__/fuzz.test.ts (4)

1-9: LGTM!

Clear documentation header and correct imports for the binary compression APIs.


10-39: LGTM!

The mock implementation correctly simulates binary compression behavior with a deterministic header-based approach, enabling reliable unit tests without the native module.


41-57: LGTM!

The seeded PRNG uses well-known LCG constants and generateRandomBytes correctly produces reproducible random data for testing.


59-71: LGTM!

Good parameterized test coverage across multiple data sizes with reproducible random data.

Comment thread src/__tests__/fuzz.test.ts
henningpokriefke and others added 4 commits January 13, 2026 21:32
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@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: 2

🤖 Fix all issues with AI agents
In @src/__tests__/fuzz.test.ts:
- Around line 124-133: The test "handles mixed null and random bytes" is
non-deterministic because it uses Math.random() (twice per loop); replace
Math.random() with a seeded PRNG used throughout the test so the byte pattern is
reproducible; update the test to create a seeded RNG (e.g., seedrandom or a
simple LCG) at the top of the test, call that rng() instead of Math.random()
when deciding null vs random and when generating the random byte, and then run
compressBytes/decompressBytes as before to assert equality.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 11f9ac4 and c873d88.

📒 Files selected for processing (3)
  • src/__tests__/codecs.test.ts
  • src/__tests__/fuzz.test.ts
  • src/index.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/__tests__/fuzz.test.ts (3)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
cpp/HybridZstd.cpp (4)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
src/index.tsx (5)
src/codecs/ZstdBinaryCompression.ts (2)
  • compress (18-28)
  • decompress (30-40)
src/codecs/ZstdTextCompressionAdapter.ts (2)
  • compress (18-23)
  • decompress (25-33)
cpp/react-native-zstd.cpp (4)
  • compress (10-35)
  • compress (10-13)
  • decompress (38-80)
  • decompress (38-40)
cpp/HybridZstd.hpp (4)
  • data (12-12)
  • data (13-13)
  • data (16-16)
  • data (17-17)
nitrogen/generated/shared/c++/HybridZstdSpec.hpp (4)
  • data (52-52)
  • data (53-53)
  • data (54-54)
  • data (55-55)
src/__tests__/codecs.test.ts (3)
src/codecs/types.ts (1)
  • CompressionV1 (22-38)
src/codecs/ZstdBinaryCompression.ts (1)
  • ZstdBinaryCompression (11-41)
src/codecs/ZstdTextCompressionAdapter.ts (1)
  • ZstdTextCompressionAdapter (11-34)
🔇 Additional comments (19)
src/index.tsx (3)

6-13: LGTM!

The string-based API is clearly marked for backward compatibility, and the implementation correctly delegates to the hybrid object methods.


15-25: LGTM!

The binary-safe API is well-structured and mirrors the string-based API pattern. The signatures correctly align with the C++ HybridZstdSpec (compressBytes(ArrayBuffer, double) and decompressBytes(ArrayBuffer)). The default compression level of 3 is consistent across both APIs.


27-29: LGTM!

Clean re-exports of codec adapters and types, providing a unified public API surface from the main entry point.

src/__tests__/fuzz.test.ts (6)

1-9: LGTM!

Clear documentation header explaining the purpose of the fuzz tests.


10-39: LGTM!

The mock correctly simulates binary compression behavior with a 4-byte header (0x5a, 0x53, 0x54, 0x44 = "ZSTD"), providing a consistent round-trip mechanism for testing without the native module.


41-60: LGTM!

The seeded PRNG implementation using a linear congruential generator provides reproducible random data generation, which is essential for reliable fuzz testing.


62-74: LGTM!

Well-structured parameterized tests covering a good range of input sizes from tiny to large.


87-99: LGTM!

Good coverage of edge-case sizes around powers of two and boundary values.


101-122: LGTM!

Good coverage of deterministic byte patterns that could trigger compression edge cases.

src/__tests__/codecs.test.ts (10)

1-9: LGTM!

Clear documentation and proper imports of both codec adapters and the interface type.


11-44: LGTM!

The mock is consistent with fuzz.test.ts and correctly simulates both string-based and binary compression paths using the same header convention.


46-71: LGTM!

Interface compliance tests properly verify that the codec exposes the expected methods and returns correct types.


73-105: LGTM!

Excellent coverage of binary safety edge cases including null bytes, 0xff bytes, all byte values (0-255), and empty arrays. These tests validate the core binary-safe behavior that distinguishes the new API from the string-based API.


107-129: LGTM!

Good coverage of compression level handling through constructor, per-call parameter, and default behavior.


131-146: LGTM!

This is an important test case. It validates that ZstdBinaryCompression correctly handles Uint8Array views with non-zero offsets, which aligns with the implementation in src/codecs/ZstdBinaryCompression.ts that uses bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength).


148-173: LGTM!

Interface compliance tests for the text adapter mirror the binary adapter tests appropriately.


175-208: LGTM!

Comprehensive text handling tests covering UTF-8, JSON, special characters (including multi-byte Unicode and emoji), and empty strings.


210-226: LGTM!

Compression level tests for the text adapter are consistent with the binary adapter tests.


228-254: LGTM!

The adapter comparison tests ensure both implementations conform to the same CompressionV1 interface and produce equivalent results for text data. This validates interoperability between the two adapters.

Comment thread src/__tests__/fuzz.test.ts
Comment thread src/__tests__/fuzz.test.ts

@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: 2

🤖 Fix all issues with AI agents
In @src/__tests__/fuzz.test.ts:
- Around line 32-36: The mock decompressBytes assumes at least 4 bytes and will
throw for empty or short ArrayBuffers; add a guard in the decompressBytes
implementation to check the input length (via new Uint8Array(data).length) and
handle cases where length < 4 by returning an empty ArrayBuffer (or the
appropriate empty/identity value your code expects) instead of slicing, and add
a unit test in fuzz.test.ts that calls decompressBytes with an empty ArrayBuffer
to assert the expected behavior.
- Around line 102-136: Add two additional test cases to the existing "specific
byte patterns" suite to cover all-zeros and all-0xFF buffers: create a 1000-byte
Uint8Array filled with 0x00 and another filled with 0xFF, compress them with
compressBytes(..., 3), decompress with decompressBytes(...), and assert the
decompressed output equals the original buffer; reuse the existing style and
helper functions (compressBytes, decompressBytes, createSeededRandom) and place
the new it(...) blocks alongside the repeating/incrementing/mixed tests.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c873d88 and 818cf8c.

📒 Files selected for processing (1)
  • src/__tests__/fuzz.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/__tests__/fuzz.test.ts (2)
cpp/HybridZstd.cpp (4)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
🔇 Additional comments (2)
src/__tests__/fuzz.test.ts (2)

62-74: LGTM!

Good coverage of size categories from tiny to large. The parameterized test structure is clean and maintainable.


76-100: LGTM!

Good use of seeded randomness for reproducibility. Edge-case sizes around powers of two are valuable for catching buffer boundary issues.

Comment thread src/__tests__/fuzz.test.ts
Comment thread src/__tests__/fuzz.test.ts

@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

🤖 Fix all issues with AI agents
In @src/__tests__/fuzz.test.ts:
- Around line 45-51: The PRNG produced by createSeededRandom can return exactly
1.0 when seed equals 0x7fffffff, causing downstream Math.floor(random() * 256)
to yield 256; to fix it, change the divisor in the returned function from
0x7fffffff to 0x80000000 so the generated value is in [0,1) (i.e., update the
expression that returns seed / 0x7fffffff to use seed / 0x80000000), leaving the
LCG update and masking logic in createSeededRandom unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 818cf8c and b6cb081.

📒 Files selected for processing (1)
  • src/__tests__/fuzz.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/__tests__/fuzz.test.ts (2)
cpp/HybridZstd.cpp (4)
  • compressBytes (54-81)
  • compressBytes (54-56)
  • decompressBytes (84-109)
  • decompressBytes (84-85)
src/index.tsx (2)
  • compressBytes (16-21)
  • decompressBytes (23-25)
🔇 Additional comments (6)
src/__tests__/fuzz.test.ts (6)

1-9: LGTM!

Clear documentation and appropriate imports for the fuzz test suite.


10-43: Well-structured mock with proper edge case handling.

The mock correctly simulates round-trip behavior with header-based approach. The guard for short buffers on line 36-38 properly handles edge cases, returning an empty ArrayBuffer instead of throwing on malformed input.


53-64: Helper function looks correct.

The generateRandomBytes helper properly creates deterministic test data. The potential byte overflow issue is addressed in the PRNG comment above.


66-78: Good coverage of size ranges.

The parameterized test properly validates round-trip integrity across different data sizes. The as ArrayBuffer cast is safe here since generateRandomBytes creates fresh Uint8Array instances with matching buffer sizes.


80-104: Excellent edge-case coverage.

The edge-case size list thoughtfully includes power-of-two boundaries and their neighbors, which commonly trigger buffer-handling issues. Using the iteration index as seed ensures reproducible yet unique test data.


106-162: Comprehensive byte pattern coverage.

The test suite covers critical compression edge cases:

  • Repeating patterns that may trigger compression dictionary behavior
  • Incrementing sequences that test sequential byte handling
  • Mixed null bytes that verify binary-safety (no null termination issues)
  • Uniform buffers (all-zeros, all-0xFF) that stress run-length encoding
  • Empty buffer handling

The empty ArrayBuffer test at lines 155-161 is particularly valuable for validating the guard in the mock's decompressBytes.

Comment on lines +45 to +51
// Simple seeded PRNG for reproducible tests
const createSeededRandom = (seed: number) => {
return () => {
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
return seed / 0x7fffffff;
};
};

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

Potential off-by-one: random() can return exactly 1.0.

When seed equals 0x7fffffff after the bitwise AND, dividing by 0x7fffffff returns exactly 1.0. This causes Math.floor(random() * 256) to return 256, which is outside the valid byte range [0, 255].

Proposed fix
 const createSeededRandom = (seed: number) => {
   return () => {
     seed = (seed * 1103515245 + 12345) & 0x7fffffff;
-    return seed / 0x7fffffff;
+    return seed / 0x80000000;
   };
 };

Dividing by 0x80000000 ensures the result is strictly in [0, 1).

🤖 Prompt for AI Agents
In @src/__tests__/fuzz.test.ts around lines 45 - 51, The PRNG produced by
createSeededRandom can return exactly 1.0 when seed equals 0x7fffffff, causing
downstream Math.floor(random() * 256) to yield 256; to fix it, change the
divisor in the returned function from 0x7fffffff to 0x80000000 so the generated
value is in [0,1) (i.e., update the expression that returns seed / 0x7fffffff to
use seed / 0x80000000), leaving the LCG update and masking logic in
createSeededRandom unchanged.

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.

1 participant