fbr

Pure MoonBit Brotli (.br) encoder and decoder with split encode/decode packages

brotli
br
compression
decompression
encoder
decoder
moon add hustcer/fbr@0.8.2
Download zip
Author
Version
0.8.2
License
Apache-2.0
Last updated
6 days ago
Downloads
4K
README

#fbr - Brotli for MoonBit

fbr is a pure MoonBit Brotli (.br) encoder and decoder.

The package is split so applications can choose the smallest dependency graph for their use case:

  • Decode only: import hustcer/fbr/decode
  • Encode only: import hustcer/fbr/encode
  • Full convenience API: import hustcer/fbr

#Small Artifacts By Design

fbr keeps decode, encode, and the root facade in separate packages. A decode-only application can import hustcer/fbr/decode without making encoder code reachable, and an encode-only application can import hustcer/fbr/encode without making decoder code reachable. The root hustcer/fbr package remains a convenience facade for applications that want both sides.

The Brotli static dictionary is also stored in a wasm-friendly form. The RFC dictionary is 122,784 bytes; representing it as one huge FixedArray[Byte] literal can make wasm-gc emit one initialization instruction per byte. fbr stores the bytes as chunks and blits them into the final FixedArray[Byte], so the dictionary is data again instead of a large block of generated startup code. This keeps decode-only wasm-gc artifacts close to the real data size without changing the public API, the dictionary bytes, or the codec behavior.

Recent just size js,wasm-gc,native output:

targetcasesize
wasm-gcdecode-only159.1 kB
wasm-gcencode-only211.8 kB
wasm-gcfull232.2 kB
jsdecode-only605.7 kB
jsencode-only973.4 kB
jsfull1.0 MB
nativedecode-only507.4 kB
nativeencode-only728.2 kB
nativefull790.0 kB

#Installation

moon add hustcer/fbr

Or add this to moon.mod.json:

{ "deps": { "hustcer/fbr": "0.8.2" } }

#Decode Only

let plain = @decode.unbrotli_sync(compressed)

Use hustcer/fbr/decode in moon.pkg:

import {
"hustcer/fbr/decode"
}

#Encode Only

let compressed = @encode.brotli_sync(
data,
opts={
..@encode.BrotliOptions::default(),
quality: 9,
window_bits: 22,
},
)

Use hustcer/fbr/encode in moon.pkg:

import {
"hustcer/fbr/encode"
}

#Full API

The root package exposes facade wrappers and type aliases for convenience:

let compressed = @fbr.brotli_sync(data)
let plain = @fbr.unbrotli_sync(compressed)

Use hustcer/fbr in moon.pkg:

import {
"hustcer/fbr"
}

Size-sensitive applications should import hustcer/fbr/decode or hustcer/fbr/encode directly instead of the root facade.

#Streams

UnbrotliStream incrementally decompresses input chunks and emits output through ondata as complete Brotli meta-blocks become available, so decoded data is delivered before the final push. A few caveats:

  • Memory is not window-bounded. The stream keeps the entire decompressed output for its lifetime, so memory grows with total output size rather than with the window. For very large payloads, prefer one-shot unbrotli_sync.
  • Re-decode is per-push. Each meta-block is decoded speculatively and rolled back when the input is incomplete, so a single large meta-block fed in many tiny chunks is re-decoded on every push (quadratic in the worst case). Prefer reasonably sized chunks over byte-at-a-time input.
  • UnbrotliOptions.out is ignored. Streaming output is delivered only through ondata; unlike one-shot unbrotli_sync, the stream does not write into a caller-provided buffer.

BrotliStream is a buffering convenience wrapper. It stores input chunks until push(chunk, final_=true), then calls brotli_sync and emits the result.

let stream = @encode.BrotliStream::new()
stream.set_ondata((chunk, is_final) => {
// handle compressed chunk
})
stream.push(chunk1)
stream.push(chunk2, final_=true)

#Status

  • brotli_sync supports quality levels 0..=11.
  • unbrotli_sync decodes RFC 7932 Brotli streams, including static dictionary references and transforms.
  • BrotliOptions exposes quality, window_bits, and max_input_size.
  • UnbrotliOptions exposes an optional caller-provided output buffer (used by unbrotli_sync; ignored by UnbrotliStream), max_output_size, and max_input_size.
  • In production builds, decode imports only common.
  • In production builds, encode imports only common. Its moon.pkg imports decode only for white-box tests.
  • The root package imports common, decode, and encode, and contains only facade wrappers, type aliases, and shared error/default aliases.
  • BrotliStream is a buffering convenience wrapper, not a true incremental Brotli encoder.
  • The static dictionary is built from byte chunks instead of one byte-per-entry FixedArray[Byte] literal.
  • tools/ includes fixture, conformance, fuzz, size, and benchmark scripts.
  • q0 and q1 currently emit valid stored streams and are not intended to match Google's low-quality compression ratio. q10 and q11 are valid but may still be larger than Google Brotli output; see the release report for measured data.

#Size Verification

Run the JS artifact split check before release:

just size

To inspect both JS and wasm-gc artifact sizes:

just size js,wasm-gc

The check builds three temporary release applications:

  • decode-only, importing hustcer/fbr/decode
  • encode-only, importing hustcer/fbr/encode
  • full, importing hustcer/fbr

For the JS target it scans the linked artifact and fails if the decode-only artifact contains encode package markers, or the encode-only artifact contains decode package markers.

For wasm-gc, the same command reports the linked artifact sizes for the three import shapes. This is useful for catching large static-data representation regressions during review: the Brotli dictionary should be carried as bytes/data plus a small number of chunk copies, not as tens of thousands of array.set initialization instructions.

See docs/brotli-pkg.md for the package split rationale and docs/brotli_release_report.md for recorded Brotli benchmark data.

#
BrotliOptions

future suffix-tree/Zopfli backend.

#
BrotliStream

Buffering convenience wrapper around brotli_sync.

Input chunks are buffered until the final push, then encoded with brotli_sync and emitted through ondata. This is not a true incremental Brotli encoder.

#
FbrError

Error raised by Brotli decoding, decompression, and encoding APIs.

code is stable enough for branching in callers. message may include additional context such as the failed format check.

#
FbrErrorCode

Error categories reported by Brotli operations.

The public decode/encode APIs raise FbrError with one of these codes. The code is intended for programmatic handling while the message gives a human-readable explanation.

#
FbrStreamHandler

Callback wrapper used by stream-style APIs.

The wrapped function receives (data, is_final). data is the produced output chunk and is_final is true for the final callback of a stream.

#
UnbrotliOptions

Options for Brotli decompression.

out lets callers provide a reusable output buffer. The buffer must fit the full decompressed payload when supplied. The Brotli decoder may allocate a ring buffer up to 1 << window_bits from the stream header; the default max_output_size still caps the final output returned by the sync API.

#
UnbrotliStream

Incremental Brotli decompressor.

Input chunks are decoded as soon as complete Brotli meta-blocks are available. Produced chunks are emitted through ondata.

#
brotli_sync

fn brotli_sync(data : FixedArray[Byte], opts? :
BrotliOptions
) -> FixedArray[Byte] raise
FbrError

#
default_max_input_size

let default_max_input_size : Int

#
default_max_output_size

let default_max_output_size : Int

#
fbr_error_code_to_int

fn fbr_error_code_to_int(code :
FbrErrorCode
) -> Int

#
unbrotli_sync

fn unbrotli_sync(data : FixedArray[Byte], opts? :
UnbrotliOptions
) -> FixedArray[Byte] raise
FbrError

Source Files

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io