<div align="center">

# [<picture><source media="(prefers-color-scheme: dark)" type="image/png" srcset="https://cdn.jsdelivr.net/gh/nberlette/brocha/.github/assets/banner.png" /><source media="(prefers-color-scheme: light)" type="image/png" srcset="https://cdn.jsdelivr.net/gh/nberlette/brocha/.github/assets/banner_light.png" /><source type="image/svg+xml" srcset="https://cdn.jsdelivr.net/gh/nberlette/brocha/.github/assets/banner.svg" /><img src="https://cdn.jsdelivr.net/gh/nberlette/brocha/.github/assets/banner.png" alt="@nick/brocha" /></picture>][npm:brocha]

![][badge-jsr-score] ![][badge-jsr-pkg] ![][badge-npm]

</div>

---

This package provides a blazing fast TypeScript implementation of the [Brotli]
decompression algorithm, suitable for use in any ES2015+ environment. It offers
a performant, portable, and reliable alternative to existing solutions, with
support for custom dictionaries and a small footprint.

[Benchmarks](#benchmarks) show it to be nearly as fast as WebAssembly-based
decoders, and significantly faster than JavaScript-only alternatives.

## Install

<picture align="left" width="32" height="48">
  <source media="(prefers-color-scheme: dark)" srcset="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem&color=%23fff" />
  <img align="left" src="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem" alt="Deno" width="32" height="48" />
</picture>

```sh
deno add npm:brocha
```

<img align="left" src="https://api.iconify.design/logos:npm.svg?height=2rem&width=2rem&inline=true" alt="NPM" width="32" height="48" />

```sh
npm i brocha
```

<details><summary><b><u>Additional NPM install methods</u></b> ⤵︎</summary><br>

<img align="right" src="https://api.iconify.design/logos:bun.svg" alt="Bun" width="32" height="48" />

```sh
bun add brocha
```

<img align="right" src="https://api.iconify.design/devicon:pnpm.svg?height=2.5rem&width=2.5rem&inline=true" alt="PNPM" width="32" height="48" />

```sh
pnpm add brocha
```

<img align="right" src="https://api.iconify.design/logos:yarn.svg?height=2rem&width=2rem&inline=true" alt="Yarn" width="32" height="48" />

```sh
yarn add brocha
```

</details>

---

#### [JSR (The JavaScript Registry)][JSR]

This package is dual-published to both [npm] and [JSR], and can be installed
using any of your favorite package managers or CDNs. The JSR distribution is
available at [`@nick/brocha`][JSR].

<picture align="left" width="32" height="48">
  <source media="(prefers-color-scheme: dark)" srcset="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem&color=%23fff" />
  <img align="left" src="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem" alt="Deno" width="32" height="48" />
</picture>

```sh
deno add jsr:@nick/brocha
```

<img align="left" src="https://api.iconify.design/simple-icons:jsr.svg?color=%23fb0" alt="JSR" width="32" height="48" />

```sh
npx jsr add @nick/brocha
```

<details><summary><b><u>Additional JSR install methods</u></b> ⤵︎</summary><br>

<img align="right" src="https://api.iconify.design/logos:bun.svg" alt="Bun" width="32" height="48" />

```sh
bunx jsr add @nick/brocha
```

<img align="right" src="https://api.iconify.design/devicon:pnpm.svg?height=2.5rem&width=2.5rem&inline=true" alt="PNPM" width="32" height="48" />

```sh
pnpm dlx jsr add @nick/brocha
```

<img align="right" src="https://api.iconify.design/logos:yarn.svg?height=2rem&width=2rem&inline=true" alt="Yarn" width="32" height="48" />

```sh
yarn dlx jsr add @nick/brocha
```

</details>

---

#### Importing from a CDN

Thanks to the widespread CDN support for NPM-distributed packages, `brocha` is
available to use right in the browser! Simply import it from your favorite CDN:

##### [esm.sh](https://esm.sh/brocha)

```ts
import { decompress } from "https://esm.sh/brocha";
```

<details><summary><b><u>Additional CDN import methods</u></b> ⤵︎</summary><br>

##### [Skypack](https://cdn.skypack.dev/brocha)

```ts
import { decompress } from "https://cdn.skypack.dev/brocha";
```

##### [UNPKG](https://unpkg.com/brocha)

```ts
import { decompress } from "https://unpkg.com/brocha";
```

##### [jsDelivr](https://cdn.jsdelivr.net/npm/brocha)

```ts
import { decompress } from "https://cdn.jsdelivr.net/npm/brocha";
```

</details>

---

## Usage

```ts
import { decompress } from "@nick/brocha";

const response = await fetch("file:///compressed.br");
const compressedData = new Uint8Array(await response.arrayBuffer());

const decompressedData = decompress(compressedData);

console.log(
  `Decompressed ${compressedData.length}B -> ${decompressedData.length}B`,
);
```

> [!TIP]
>
> The `decompress` function is ready to use immediately upon import. Simply pass
> in your Brotli-compressed data and receive the decompressed `Uint8Array`.

---

## API

### `decompress`

Decompresses a [`BufferSource`] object containing Brotli-encoded data, returning
a new `Uint8Array` instance with the decompressed data.

#### Signature

```ts ignore
decompress(input: BufferSource, options?: BrotliDecodeOptions): Uint8Array;
```

#### Parameters

- `input`: The Brotli-compressed [`BufferSource`] to be decoded.
- `options`: Optional decompression options, allowing you to use a custom
  dictionary.

#### Returns

- `Uint8Array`: The decompressed data.

> [!NOTE]
>
> The [`decompress`](#decompress) function is completely synchronous, and
> requires no additional setup beyond importing it from the `brocha` package.

#### Examples

##### Decompressing a Brotli-compressed file

```ts no-eval
import { decompress } from "@nick/brocha";
import * as fs from "node:fs";

const compressed = fs.readFileSync("data.br");
const decompressed = decompress(compressed);

const inputKB = (compressed.byteLength / 1024).toFixed(0);
const outputKB = (decompressed.byteLength / 1024).toFixed(0);
console.log(
  `${inputKB}K → ${outputKB}K (+${(outputKB / inputKB).toFixed(2)}x)`,
);
// Example log: "115K → 483K (+4.20x)"
```

##### Using a custom dictionary

```ts no-eval
import { decompress } from "@nick/brocha";

const compressedData = /* Brotli-compressed data */;
const dictionary = new Uint8Array([/* custom dictionary bytes */]);

const options = { customDictionary: dictionary };
const decompressedData = decompress(compressedData, options);

console.log(decompressedData);
```

> [!WARNING]
>
> Custom dictionaries are an advanced feature, and must be used with care. If an
> invalid dictionary is provided, or if it does not match the one that was used
> to compress the data you are trying to decompress, the decompression process
> will either fail with an error or return corrupted data.

---

### `BrotliDecodeOptions`

Options to customize the behavior of the Brotli decompression process.

```ts
interface BrotliDecodeOptions {
  customDictionary?: BufferSource | null;
}
```

#### `customDictionary` (`BufferSource | null`)

Custom dictionary to use for the Brotli decompression.

##### Default

`null` _(uses the default dictionary)_

##### Remarks

The dictionary **must** be a valid Brotli dictionary that **exactly** matches
the one used when the input data was compressed. Otherwise, the decompressor
will either throw an exception or corrupted data will be returned.

---

## Benchmarks

This package is designed to be lightweight and fast, with a focus on performance
and efficiency. The following benchmarks were run on a 2021 MacBook Pro with an
M1 Pro chip using Deno 2.1.2.

The results demonstrate the performance of this package compared to other
popular Brotli decompression tools, suggesting it is a viable alternative to
existing [WebAssembly-based solutions](#webassembly-decoders).

- Performance is mostly on par with WebAssembly decoders like [brotli-wasm],
  which typically show small speed advantages of ~15-20% over this package.
- Compared to other pure-JS implementations (specifically [npm:brotli]),
  `brocha` consistently clocks speeds ~1.75x faster across all benchmarks.
- Performance is about 35-50% that of the native Node.js `node:zlib` module.

> [!NOTE]
>
> The native `node:zlib` module is written in C++ and is highly optimized for
> performance. While _"~2.5x slower"_ sounds like a poor result, it's actually
> quite fast for a pure JavaScript implementation, which will never be able to
> match the performance of a native module.

```sh
> deno bench -A --no-check

benchmark             time/iter (avg)        iter/s      (min … max)           p75      p99     p995
--------------------- ----------------------------- --------------------- --------------------------

group basic json (6.5 KB -> 27.5 KB)

jsr:@nick/brocha             399.0 µs         2,506 (324.8 µs …   3.1 ms) 381.8 µs   1.6 ms   2.1 ms
npm:brotli                   666.1 µs         1,501 (553.7 µs …   3.0 ms) 626.2 µs   1.9 ms   2.3 ms
npm:brotli-wasm              322.5 µs         3,101 (265.4 µs …   7.0 ms) 276.6 µs   1.7 ms   2.5 ms
npm:brotli-dec-wasm          306.7 µs         3,261 (279.2 µs …   4.7 ms) 288.3 µs 915.1 µs   1.6 ms
node:zlib                    144.5 µs         6,921 (136.5 µs …   1.8 ms) 141.8 µs 194.1 µs 245.7 µs

summary
  jsr:@nick/brocha
     2.76x slower than node:zlib
     1.30x slower than npm:brotli-dec-wasm
     1.24x slower than npm:brotli-wasm
     1.67x faster than npm:brotli

group dprint-plugin-graphql.wasm (147 KB -> 768 KB)

jsr:@nick/brocha               6.5 ms         154.0 (  6.0 ms …   7.7 ms)   6.7 ms   7.7 ms   7.7 ms
npm:brotli                    12.2 ms          82.1 ( 10.6 ms …  57.6 ms)  11.3 ms  57.6 ms  57.6 ms
npm:brotli-wasm                6.5 ms         153.3 (  5.9 ms …  31.6 ms)   6.1 ms  31.6 ms  31.6 ms
npm:brotli-dec-wasm            6.3 ms         158.8 (  6.2 ms …   7.1 ms)   6.3 ms   7.1 ms   7.1 ms
node:zlib                      3.4 ms         292.2 (  3.2 ms …   4.6 ms)   3.5 ms   4.2 ms   4.6 ms

summary
  jsr:@nick/brocha
     1.90x slower than node:zlib
     1.03x slower than npm:brotli-dec-wasm
     1.00x faster than npm:brotli-wasm
     1.88x faster than npm:brotli

group dprint-plugin-jupyter.wasm (354 KB -> 1.68 MB)

jsr:@nick/brocha              14.4 ms          69.5 ( 13.9 ms …  15.9 ms)  14.5 ms  15.9 ms  15.9 ms
npm:brotli                    24.6 ms          40.6 ( 23.9 ms …  26.4 ms)  24.8 ms  26.4 ms  26.4 ms
npm:brotli-wasm               13.9 ms          71.8 ( 13.6 ms …  15.0 ms)  14.1 ms  15.0 ms  15.0 ms
npm:brotli-dec-wasm           14.9 ms          67.3 ( 14.5 ms …  15.4 ms)  15.0 ms  15.4 ms  15.4 ms
node:zlib                      8.4 ms         119.5 (  8.0 ms …   9.3 ms)   8.4 ms   9.3 ms   9.3 ms

summary
  jsr:@nick/brocha
     1.72x slower than node:zlib
     1.03x slower than npm:brotli-wasm
     1.03x faster than npm:brotli-dec-wasm
     1.71x faster than npm:brotli

group dprint-plugin-typescript.wasm (746 KB -> 4.01 MB)

jsr:@nick/brocha              37.5 ms          26.7 ( 32.8 ms …  42.2 ms)  38.6 ms  42.2 ms  42.2 ms
npm:brotli                    60.5 ms          16.5 ( 55.6 ms …  73.5 ms)  62.9 ms  73.5 ms  73.5 ms
npm:brotli-wasm               31.9 ms          31.4 ( 30.8 ms …  33.1 ms)  32.6 ms  33.1 ms  33.1 ms
npm:brotli-dec-wasm           33.8 ms          29.6 ( 32.9 ms …  35.0 ms)  34.3 ms  35.0 ms  35.0 ms
node:zlib                     18.1 ms          55.3 ( 17.4 ms …  20.2 ms)  18.5 ms  20.2 ms  20.2 ms

summary
  jsr:@nick/brocha
     2.07x slower than node:zlib
     1.18x slower than npm:brotli-wasm
     1.11x slower than npm:brotli-dec-wasm
     1.61x faster than npm:brotli

group ten megs of lipsum (1.71 MB -> 9.77 MB)

jsr:@nick/brocha              64.1 ms          15.6 ( 60.1 ms …  69.1 ms)  66.9 ms  69.1 ms  69.1 ms
npm:brotli                   109.6 ms           9.1 (104.6 ms … 115.2 ms) 114.4 ms 115.2 ms 115.2 ms
npm:brotli-wasm               48.5 ms          20.6 ( 46.8 ms …  51.2 ms)  49.8 ms  51.2 ms  51.2 ms
npm:brotli-dec-wasm           52.3 ms          19.1 ( 49.9 ms …  54.5 ms)  53.5 ms  54.5 ms  54.5 ms
node:zlib                     28.0 ms          35.8 ( 25.4 ms …  30.2 ms)  28.8 ms  30.2 ms  30.2 ms

summary
  jsr:@nick/brocha
     2.29x slower than node:zlib
     1.32x slower than npm:brotli-wasm
     1.23x slower than npm:brotli-dec-wasm
     1.71x faster than npm:brotli
```

> The `decompress` function from this package is the baseline, with some other
> popular Brotli decompression tools for comparison.

---

### Prior Art

This project was adapted from the original [brotli] source code, developed by
the Google Brotli team and licensed under the MIT license.

#### WebAssembly Decoders

- [brotli-decompressor]
- [brotli-dec-wasm]
- [brotli-wasm]

#### JavaScript Decoders

- [npm:brotli]

---

<div align="center">

##### [MIT] © [Nicholas Berlette]. All rights reserved.

###### [GitHub] • [Issues] • [JSR] • [NPM]

<br>

[![JSR][JSR-badge]][JSR] [![JSR][badge-jsr-score]][JSR]

</div>

[MIT]: https://nick.mit-license.org "MIT © 2024+ Nicholas Berlette. All rights reserved."
[Nicholas Berlette]: https://github.com/nberlette "Nicholas Berlette on GitHub"
[GitHub]: https://github.com/nberlette/brocha "View the @nick/brocha project on GitHub"
[Issues]: https://github.com/nberlette/brocha/issues "View issues for the @nick/brocha project on GitHub"
[JSR]: https://jsr.io/@nick/brocha/doc "View the @nick/brocha documentation on jsr.io"
[NPM]: https://www.npmjs.com/package/brocha "View the brocha package on npm"
[brotli-decompressor]: https://crates.io/crates/brotli-decompressor "View the brotli-decompressor crate on crates.io"
[brotli-wasm]: https://crates.io/brotli-wasm "View the brotli-wasm crate on crates.io"
[brotli-dec-wasm]: https://crates.io/brotli-dec-wasm "View the brotli-dec-wasm crate on crates.io"
[jsr-badge]: https://jsr.io/badges/@nick "Install @nick/brocha with JSR"
[badge-npm]: https://img.shields.io/npm/v/brocha?logo=npm&color=firebrick&labelColor=firebrick&label= "View brocha on npm"
[badge-jsr-nick]: https://jsr.io/badges/@nick "View all of @nick's packages on jsr.io"
[badge-jsr-pkg]: https://jsr.io/badges/@nick/brocha "View @nick/brocha on jsr.io"
[badge-jsr-score]: https://jsr.io/badges/@nick/brocha/score "View the score for @nick/brocha on jsr.io"
[brotli]: https://github.com/google/brotli "View the Brotli project on GitHub"
[npm:brotli]: https://www.npmjs.com/package/brotli "View the brotli package on npm"
[npm:brocha]: https://www.npmjs.com/package/brocha "View the brocha package on npm"
[`BufferSource`]: https://developer.mozilla.org/en-US/docs/Web/API/BufferSource "MDN Web Docs: BufferSource"
