├── .cargo └── config.toml ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── CI.yaml │ ├── lint.yaml │ └── memory-leak-detect.yaml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .taplo.toml ├── .yarn └── releases │ └── yarn-4.9.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── __test__ ├── index.spec.ts └── package.json ├── benchmark ├── bench.ts └── package.json ├── build.rs ├── index.d.ts ├── index.js ├── memory-leak-detect.mjs ├── package.json ├── renovate.json ├── rustfmt.toml ├── src └── lib.rs ├── tsconfig.json ├── util.mjs └── yarn.lock /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-musl] 2 | rustflags = ["-C", "target-feature=-crt-static"] 3 | 4 | [target.aarch64-unknown-linux-musl] 5 | linker = "aarch64-linux-musl-gcc" 6 | rustflags = ["-C", "target-feature=-crt-static"] 7 | 8 | [target.x86_64-pc-windows-msvc] 9 | rustflags = ["-C", "target-feature=+crt-static"] 10 | 11 | [target.i686-pc-windows-msvc] 12 | rustflags = ["-C", "target-feature=+crt-static"] 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | *.ts text eol=lf merge=union 6 | *.tsx text eol=lf merge=union 7 | *.rs text eol=lf merge=union 8 | *.js text eol=lf merge=union 9 | *.json text eol=lf merge=union 10 | *.debug text eol=lf merge=union 11 | 12 | # Generated codes 13 | index.js linguist-detectable=false 14 | index.d.ts linguist-detectable=false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Brooooooklyn] 2 | -------------------------------------------------------------------------------- /.github/workflows/CI.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: snappy 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | permissions: 7 | contents: write 8 | id-token: write 9 | 'on': 10 | push: 11 | branches: 12 | - main 13 | tags-ignore: 14 | - '**' 15 | paths-ignore: 16 | - '**/*.md' 17 | - LICENSE 18 | - '**/*.gitignore' 19 | - .editorconfig 20 | - docs/** 21 | pull_request: null 22 | jobs: 23 | build: 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | settings: 28 | - host: macos-latest 29 | target: x86_64-apple-darwin 30 | build: yarn build --target x86_64-apple-darwin 31 | - host: windows-latest 32 | build: yarn build --target x86_64-pc-windows-msvc 33 | target: x86_64-pc-windows-msvc 34 | - host: windows-latest 35 | build: | 36 | yarn build --target i686-pc-windows-msvc 37 | yarn test 38 | target: i686-pc-windows-msvc 39 | - host: ubuntu-latest 40 | target: x86_64-unknown-linux-gnu 41 | build: CC=clang yarn build --target x86_64-unknown-linux-gnu --use-napi-cross 42 | - host: ubuntu-latest 43 | target: x86_64-unknown-linux-musl 44 | build: yarn build -x --target x86_64-unknown-linux-musl 45 | - host: macos-latest 46 | target: aarch64-apple-darwin 47 | build: yarn build --target aarch64-apple-darwin 48 | - host: ubuntu-latest 49 | target: aarch64-unknown-linux-gnu 50 | build: CC=clang yarn build --target aarch64-unknown-linux-gnu --use-napi-cross 51 | - host: ubuntu-latest 52 | target: armv7-unknown-linux-gnueabihf 53 | setup: | 54 | sudo apt-get update 55 | sudo apt-get install gcc-arm-linux-gnueabihf -y 56 | build: yarn build --target armv7-unknown-linux-gnueabihf --use-napi-cross 57 | - host: ubuntu-latest 58 | target: aarch64-linux-android 59 | build: yarn build --target aarch64-linux-android 60 | - host: ubuntu-latest 61 | target: armv7-linux-androideabi 62 | build: yarn build --target armv7-linux-androideabi 63 | - host: ubuntu-latest 64 | target: aarch64-unknown-linux-musl 65 | build: yarn build --target aarch64-unknown-linux-musl -x 66 | - host: windows-latest 67 | target: aarch64-pc-windows-msvc 68 | build: yarn build --target aarch64-pc-windows-msvc 69 | name: stable - ${{ matrix.settings.target }} - node@20 70 | runs-on: ${{ matrix.settings.host }} 71 | steps: 72 | - uses: actions/checkout@v4 73 | - name: Setup node 74 | uses: actions/setup-node@v4 75 | if: ${{ !matrix.settings.docker }} 76 | with: 77 | node-version: 20 78 | cache: yarn 79 | - name: Install 80 | uses: dtolnay/rust-toolchain@stable 81 | with: 82 | toolchain: stable 83 | targets: ${{ matrix.settings.target }} 84 | - name: Cache cargo 85 | uses: actions/cache@v4 86 | with: 87 | path: | 88 | ~/.cargo/registry 89 | ~/.cargo/git/db/ 90 | .cargo-cache 91 | ~/.napi-rs 92 | target/ 93 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 94 | - uses: goto-bus-stop/setup-zig@v2 95 | if: ${{ contains(matrix.settings.target, 'musl') }} 96 | with: 97 | version: 0.13.0 98 | - name: Install cargo-zigbuild 99 | uses: taiki-e/install-action@v2 100 | if: ${{ contains(matrix.settings.target, 'musl') }} 101 | env: 102 | GITHUB_TOKEN: ${{ github.token }} 103 | with: 104 | tool: cargo-zigbuild 105 | - name: Setup toolchain 106 | run: ${{ matrix.settings.setup }} 107 | if: ${{ matrix.settings.setup }} 108 | shell: bash 109 | - name: Setup node x86 110 | if: matrix.settings.target == 'i686-pc-windows-msvc' 111 | run: yarn config set supportedArchitectures.cpu "ia32" 112 | shell: bash 113 | - name: Install dependencies 114 | run: yarn install 115 | - name: Setup node x86 116 | uses: actions/setup-node@v4 117 | if: matrix.settings.target == 'i686-pc-windows-msvc' 118 | with: 119 | node-version: 20 120 | cache: yarn 121 | architecture: x86 122 | - name: Build 123 | run: ${{ matrix.settings.build }} 124 | shell: bash 125 | - name: Upload artifact 126 | uses: actions/upload-artifact@v4 127 | with: 128 | name: bindings-${{ matrix.settings.target }} 129 | path: ${{ env.APP_NAME }}.*.node 130 | if-no-files-found: error 131 | build-freebsd: 132 | runs-on: ubuntu-latest 133 | name: Build FreeBSD 134 | steps: 135 | - uses: actions/checkout@v4 136 | - name: Build 137 | id: build 138 | uses: cross-platform-actions/action@v0.28.0 139 | env: 140 | DEBUG: napi:* 141 | RUSTUP_HOME: /usr/local/rustup 142 | CARGO_HOME: /usr/local/cargo 143 | RUSTUP_IO_THREADS: 1 144 | with: 145 | operating_system: freebsd 146 | version: '14.1' 147 | memory: 8G 148 | cpu_count: 3 149 | environment_variables: 'DEBUG RUSTUP_IO_THREADS' 150 | shell: bash 151 | run: | 152 | sudo pkg install -y -f curl node libnghttp2 npm yarn 153 | sudo npm install -g yarn --ignore-scripts 154 | curl https://sh.rustup.rs -sSf --output rustup.sh 155 | sh rustup.sh -y --profile minimal --default-toolchain beta 156 | source "$HOME/.cargo/env" 157 | echo "~~~~ rustc --version ~~~~" 158 | rustc --version 159 | echo "~~~~ node -v ~~~~" 160 | node -v 161 | echo "~~~~ yarn --version ~~~~" 162 | yarn --version 163 | pwd 164 | ls -lah 165 | whoami 166 | env 167 | freebsd-version 168 | yarn install --mode=skip-build 169 | yarn build 170 | rm -rf node_modules 171 | rm -rf target 172 | rm -rf .yarn/cache 173 | - name: Upload artifact 174 | uses: actions/upload-artifact@v4 175 | with: 176 | name: bindings-freebsd 177 | path: ${{ env.APP_NAME }}.*.node 178 | if-no-files-found: error 179 | test-macOS-windows-binding: 180 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 181 | needs: 182 | - build 183 | strategy: 184 | fail-fast: false 185 | matrix: 186 | settings: 187 | - host: macos-latest 188 | target: x86_64-apple-darwin 189 | architecture: x64 190 | - host: macos-latest 191 | target: aarch64-apple-darwin 192 | architecture: arm64 193 | - host: windows-latest 194 | target: x86_64-pc-windows-msvc 195 | architecture: x64 196 | node: 197 | - '18' 198 | - '20' 199 | runs-on: ${{ matrix.settings.host }} 200 | steps: 201 | - uses: actions/checkout@v4 202 | - name: Setup node 203 | uses: actions/setup-node@v4 204 | with: 205 | node-version: ${{ matrix.node }} 206 | cache: yarn 207 | architecture: ${{ matrix.settings.architecture }} 208 | - name: Install dependencies 209 | run: yarn install --mode=skip-build 210 | - name: Download artifacts 211 | uses: actions/download-artifact@v4 212 | with: 213 | name: bindings-${{ matrix.settings.target }} 214 | path: . 215 | - name: List packages 216 | run: ls -R . 217 | shell: bash 218 | - name: Test bindings 219 | run: yarn test 220 | test-linux-x64-gnu-binding: 221 | name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} 222 | needs: 223 | - build 224 | strategy: 225 | fail-fast: false 226 | matrix: 227 | node: 228 | - '18' 229 | - '20' 230 | runs-on: ubuntu-latest 231 | steps: 232 | - uses: actions/checkout@v4 233 | - name: Setup node 234 | uses: actions/setup-node@v4 235 | with: 236 | node-version: ${{ matrix.node }} 237 | cache: yarn 238 | - name: Install dependencies 239 | run: yarn install 240 | - name: Download artifacts 241 | uses: actions/download-artifact@v4 242 | with: 243 | name: bindings-x86_64-unknown-linux-gnu 244 | path: . 245 | - name: List packages 246 | run: ls -R . 247 | shell: bash 248 | - name: Test bindings 249 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test 250 | test-linux-x64-musl-binding: 251 | name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} 252 | needs: 253 | - build 254 | strategy: 255 | fail-fast: false 256 | matrix: 257 | node: 258 | - '18' 259 | - '20' 260 | runs-on: ubuntu-latest 261 | steps: 262 | - uses: actions/checkout@v4 263 | - name: Setup node 264 | uses: actions/setup-node@v4 265 | with: 266 | node-version: ${{ matrix.node }} 267 | cache: yarn 268 | - name: Install dependencies 269 | run: | 270 | yarn config set supportedArchitectures.libc "musl" 271 | yarn install --mode=skip-build 272 | - name: Download artifacts 273 | uses: actions/download-artifact@v4 274 | with: 275 | name: bindings-x86_64-unknown-linux-musl 276 | path: . 277 | - name: List packages 278 | run: ls -R . 279 | shell: bash 280 | - name: Test bindings 281 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test 282 | test-linux-aarch64-gnu-binding: 283 | name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} 284 | needs: 285 | - build 286 | strategy: 287 | fail-fast: false 288 | matrix: 289 | node: 290 | - '20' 291 | - '22' 292 | runs-on: ubuntu-latest 293 | steps: 294 | - uses: actions/checkout@v4 295 | - name: Download artifacts 296 | uses: actions/download-artifact@v4 297 | with: 298 | name: bindings-aarch64-unknown-linux-gnu 299 | path: . 300 | - name: List packages 301 | run: ls -R . 302 | shell: bash 303 | - name: Install dependencies 304 | run: | 305 | yarn config set supportedArchitectures.cpu "arm64" 306 | yarn config set supportedArchitectures.libc "glibc" 307 | yarn install --mode=skip-build 308 | - name: Set up QEMU 309 | uses: docker/setup-qemu-action@v3 310 | with: 311 | platforms: arm64 312 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 313 | - name: Setup and run tests 314 | uses: addnab/docker-run-action@v3 315 | with: 316 | image: node:${{ matrix.node }}-slim 317 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 318 | run: | 319 | set -e 320 | yarn test 321 | ls -la 322 | test-linux-aarch64-musl-binding: 323 | name: Test bindings on aarch64-unknown-linux-musl - node@lts 324 | needs: 325 | - build 326 | runs-on: ubuntu-latest 327 | steps: 328 | - uses: actions/checkout@v4 329 | - name: Download artifacts 330 | uses: actions/download-artifact@v4 331 | with: 332 | name: bindings-aarch64-unknown-linux-musl 333 | path: . 334 | - name: List packages 335 | run: ls -R . 336 | shell: bash 337 | - name: Install dependencies 338 | run: | 339 | yarn config set supportedArchitectures.cpu "arm64" 340 | yarn config set supportedArchitectures.libc "musl" 341 | yarn install --mode=skip-build 342 | - name: Set up QEMU 343 | uses: docker/setup-qemu-action@v3 344 | with: 345 | platforms: arm64 346 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 347 | - name: Setup and run tests 348 | uses: addnab/docker-run-action@v3 349 | with: 350 | image: node:lts-alpine 351 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 352 | run: | 353 | set -e 354 | yarn test 355 | test-linux-arm-gnueabihf-binding: 356 | name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} 357 | needs: 358 | - build 359 | strategy: 360 | fail-fast: false 361 | matrix: 362 | node: 363 | - '18' 364 | - '20' 365 | runs-on: ubuntu-latest 366 | steps: 367 | - uses: actions/checkout@v4 368 | - name: Download artifacts 369 | uses: actions/download-artifact@v4 370 | with: 371 | name: bindings-armv7-unknown-linux-gnueabihf 372 | path: . 373 | - name: List packages 374 | run: ls -R . 375 | shell: bash 376 | - name: Install dependencies 377 | run: | 378 | yarn config set supportedArchitectures.cpu "arm" 379 | yarn install --mode=skip-build 380 | - name: Set up QEMU 381 | uses: docker/setup-qemu-action@v3 382 | with: 383 | platforms: arm 384 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 385 | - name: Setup and run tests 386 | uses: addnab/docker-run-action@v3 387 | with: 388 | image: node:${{ matrix.node }}-bullseye-slim 389 | options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build' 390 | run: | 391 | set -e 392 | yarn test 393 | ls -la 394 | publish: 395 | name: Publish 396 | runs-on: ubuntu-latest 397 | needs: 398 | - build-freebsd 399 | - test-macOS-windows-binding 400 | - test-linux-x64-gnu-binding 401 | - test-linux-x64-musl-binding 402 | - test-linux-aarch64-gnu-binding 403 | - test-linux-aarch64-musl-binding 404 | - test-linux-arm-gnueabihf-binding 405 | steps: 406 | - uses: actions/checkout@v4 407 | - name: Setup node 408 | uses: actions/setup-node@v4 409 | with: 410 | node-version: 20 411 | cache: yarn 412 | - name: Install dependencies 413 | run: yarn install --mode=skip-build 414 | - name: Download all artifacts 415 | uses: actions/download-artifact@v4 416 | with: 417 | path: artifacts 418 | - name: create npm dirs 419 | run: yarn napi create-npm-dirs 420 | - name: Move artifacts 421 | run: yarn artifacts 422 | - name: List packages 423 | run: ls -R ./npm 424 | shell: bash 425 | - name: Publish 426 | run: | 427 | npm config set provenance true 428 | if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 429 | then 430 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 431 | npm publish --access public 432 | elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; 433 | then 434 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 435 | npm publish --tag next --access public 436 | else 437 | echo "Not a release, skipping publish" 438 | fi 439 | env: 440 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 441 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 442 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | 11 | jobs: 12 | lint: 13 | name: Lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup node 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | cache: 'yarn' 23 | 24 | - name: Install 25 | uses: dtolnay/rust-toolchain@stable 26 | with: 27 | components: clippy, rustfmt 28 | 29 | - name: 'Install dependencies' 30 | run: yarn install --mode=skip-build 31 | 32 | - name: ESLint 33 | run: yarn lint 34 | 35 | - name: Cargo fmt 36 | run: cargo fmt -- --check 37 | 38 | - name: Clippy 39 | run: cargo clippy 40 | -------------------------------------------------------------------------------- /.github/workflows/memory-leak-detect.yaml: -------------------------------------------------------------------------------- 1 | name: Memory Leak Detect 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | 11 | jobs: 12 | Test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | cache: 'yarn' 22 | 23 | - name: Install 24 | uses: dtolnay/rust-toolchain@stable 25 | 26 | - name: 'Install dependencies' 27 | run: yarn install --mode=skip-build 28 | 29 | - name: 'Build binary' 30 | run: yarn build 31 | 32 | - name: 'Memory test' 33 | run: yarn test:mem 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # End of https://www.toptal.com/developers/gitignore/api/node 115 | 116 | 117 | #Added by cargo 118 | 119 | /target 120 | Cargo.lock 121 | 122 | *.node 123 | .pnp.* 124 | .yarn/* 125 | !.yarn/patches 126 | !.yarn/plugins 127 | !.yarn/releases 128 | !.yarn/sdks 129 | !.yarn/versions 130 | 131 | /npm -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | .yarn 3 | node_modules -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = ["node_modules/**/*.toml"] 2 | 3 | # https://taplo.tamasfe.dev/configuration/formatter-options.html 4 | [formatting] 5 | align_entries = true 6 | indent_tables = true 7 | reorder_keys = true -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [7.2.2](https://github.com/Brooooooklyn/snappy/compare/v7.2.1...v7.2.2) (2022-11-13) 2 | 3 | ### Bug Fixes 4 | 5 | - unref buffer when compress/uncompress synchronously faild ([#105](https://github.com/Brooooooklyn/snappy/issues/105)) ([8cfc521](https://github.com/Brooooooklyn/snappy/commit/8cfc5215763b5f49d454aec342cb6602b4220de4)) 6 | 7 | ## [7.2.1](https://github.com/Brooooooklyn/snappy/compare/v7.2.0...v7.2.1) (2022-10-23) 8 | 9 | ### Bug Fixes 10 | 11 | - memory leak of sync api ([#100](https://github.com/Brooooooklyn/snappy/issues/100)) ([aaa1950](https://github.com/Brooooooklyn/snappy/commit/aaa19500a35582b633d956a32beadaaa9a769c14)) 12 | 13 | # [7.2.0](https://github.com/Brooooooklyn/snappy/compare/v7.1.2...v7.2.0) (2022-10-05) 14 | 15 | ### Features 16 | 17 | - provide copyOutputData to compatible with electron >= 21 ([772abc9](https://github.com/Brooooooklyn/snappy/commit/772abc94264c212ea3abed5bea27891f0821f175)) 18 | 19 | ## [7.1.2](https://github.com/Brooooooklyn/snappy/compare/v7.1.1...v7.1.2) (2022-08-09) 20 | 21 | ### Bug Fixes 22 | 23 | - compatible issues with centos:7 and void Linux ([dbd61db](https://github.com/Brooooooklyn/snappy/commit/dbd61db3de57921bf6c6f6acd809093ba417ea26)) 24 | 25 | ## [7.1.1](https://github.com/Brooooooklyn/snappy/compare/v7.1.0...v7.1.1) (2021-12-22) 26 | 27 | ### Bug Fixes 28 | 29 | - override the package name in generated index.js ([42346fd](https://github.com/Brooooooklyn/snappy/commit/42346fdd78ad4aa91e65a5d0cd176ea716459f72)) 30 | 31 | # [7.1.0](https://github.com/Brooooooklyn/snappy/compare/v7.0.5...v7.1.0) (2021-12-22) 32 | 33 | ### Features 34 | 35 | - upgrade to napi2 ([e6cc543](https://github.com/Brooooooklyn/snappy/commit/e6cc5433eb503987d7e6f09f4346c7d317a3fccf)) 36 | 37 | ## [7.0.5](https://github.com/Brooooooklyn/snappy/compare/v7.0.4...v7.0.5) (2021-11-06) 38 | 39 | ### Bug Fixes 40 | 41 | - **decompress:** cast input to buffer ([9bdf8f3](https://github.com/Brooooooklyn/snappy/commit/9bdf8f39d4a6792798a3641b6a5b4d2d5dfe6b45)), closes [#38](https://github.com/Brooooooklyn/snappy/issues/38) 42 | 43 | ## [7.0.4](https://github.com/Brooooooklyn/snappy/compare/v7.0.3...v7.0.4) (2021-10-28) 44 | 45 | ### Bug Fixes 46 | 47 | - avoid copy input buffer ([00cdd39](https://github.com/Brooooooklyn/snappy/commit/00cdd39a9576567620216ad01c8d063bac32ac77)) 48 | 49 | ## [7.0.3](https://github.com/Brooooooklyn/snappy/compare/v7.0.2...v7.0.3) (2021-08-27) 50 | 51 | ### Bug Fixes 52 | 53 | - remove Ref usage to avoid memory leak ([c89bb2e](https://github.com/Brooooooklyn/snappy/commit/c89bb2e278a1f9ad3a2e14c790e069bc699fe492)) 54 | 55 | ## [7.0.2](https://github.com/Brooooooklyn/snappy/compare/v7.0.1...v7.0.2) (2021-08-11) 56 | 57 | ### Bug Fixes 58 | 59 | - missing asBuffer option in uncompress/uncompressSync ([ac573f8](https://github.com/Brooooooklyn/snappy/commit/ac573f8523abd7cd3642eb8557fc51f43acbc34c)) 60 | 61 | ## [7.0.1](https://github.com/Brooooooklyn/snappy/compare/v7.0.0...v7.0.1) (2021-08-04) 62 | 63 | ### Bug Fixes 64 | 65 | - native binding package name ([6dc09af](https://github.com/Brooooooklyn/snappy/commit/6dc09af844700875dd1594fb70ec3b3af37de78b)) 66 | 67 | # [7.0.0](https://github.com/Brooooooklyn/snappy/compare/v1.0.2...v7.0.0) (2021-08-04) 68 | 69 | Change package name to `snappy` [#16](https://github.com/Brooooooklyn/snappy/issues/16) . 70 | 71 | ## [1.0.2](https://github.com/Brooooooklyn/snappy/compare/v1.0.1...v1.0.2) (2021-07-22) 72 | 73 | ### Bug Fixes 74 | 75 | - linux aarch64 musl build ([1a9a475](https://github.com/Brooooooklyn/snappy/commit/1a9a475c2aef170abfd9e1e4d8eeb4d955384fa0)) 76 | 77 | ## [1.0.1](https://github.com/Brooooooklyn/snappy/compare/v1.0.0...v1.0.1) (2021-06-10) 78 | 79 | ### Performance Improvements 80 | 81 | - mimalloc as global allocator ([3fbab59](https://github.com/Brooooooklyn/snappy/commit/3fbab59ba2c095bb1b2a819eb3445ca06fc743c4)) 82 | 83 | # [1.0.0](https://github.com/Brooooooklyn/snappy/compare/df2ccd289ca2418504aff3a8fd65cc75c34ce6d8...v1.0.0) (2021-06-10) 84 | 85 | ### Features 86 | 87 | - implement compress and uncompress ([df2ccd2](https://github.com/Brooooooklyn/snappy/commit/df2ccd289ca2418504aff3a8fd65cc75c34ce6d8)) 88 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | edition = "2021" 4 | name = "napi-snappy" 5 | version = "0.1.0" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | napi = { version = "3.0.0-alpha", features = ["napi5", "serde-json"] } 12 | napi-derive = { version = "3.0.0-alpha" } 13 | snap = "1" 14 | 15 | [target.'cfg(not(target_os = "linux"))'.dependencies] 16 | mimalloc = { version = "0.1" } 17 | 18 | [target.'cfg(all(target_os = "linux", not(target_arch = "arm")))'.dependencies] 19 | mimalloc = { version = "0.1", features = ["local_dynamic_tls"] } 20 | 21 | [build-dependencies] 22 | napi-build = "2" 23 | 24 | [profile.release] 25 | codegen-units = 1 26 | lto = true 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 N-API for Rust 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `snappy` 2 | 3 | ![https://github.com/Brooooooklyn/snappy/actions](https://github.com/Brooooooklyn/snappy/workflows/CI/badge.svg) 4 | ![](https://img.shields.io/npm/dm/snappy.svg?sanitize=true) 5 | [![Install size](https://packagephobia.com/badge?p=snappy)](https://packagephobia.com/result?p=snappy) 6 | 7 | **!!! For `snappy@6.x` and below, please go to [`node-snappy`](https://github.com/kesla/node-snappy).** 8 | 9 | More background about the **6-7** changes, please read [this](https://github.com/Brooooooklyn/snappy/issues/16), Thanks [@kesla](https://github.com/kesla) . 10 | 11 | > 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn) 12 | 13 | Fastest Snappy compression library in Node.js, powered by [napi-rs](https://napi.rs) and [rust-snappy](https://github.com/BurntSushi/rust-snappy). 14 | 15 | > For small size data, [snappyjs](https://github.com/zhipeng-jia/snappyjs) is faster, and it support browser. But it doesn't have async API, which is important for Node.js program. 16 | 17 | ## Install this package 18 | 19 | ``` 20 | yarn add snappy 21 | ``` 22 | 23 | ## Support matrix 24 | 25 | | | node12 | node14 | node16 | node18 | 26 | | ---------------- | ------ | ------ | ------ | ------ | 27 | | Windows x64 | ✓ | ✓ | ✓ | ✓ | 28 | | Windows x32 | ✓ | ✓ | ✓ | ✓ | 29 | | Windows arm64 | ✓ | ✓ | ✓ | ✓ | 30 | | macOS x64 | ✓ | ✓ | ✓ | ✓ | 31 | | macOS arm64 | ✓ | ✓ | ✓ | ✓ | 32 | | Linux x64 gnu | ✓ | ✓ | ✓ | ✓ | 33 | | Linux x64 musl | ✓ | ✓ | ✓ | ✓ | 34 | | Linux arm gnu | ✓ | ✓ | ✓ | ✓ | 35 | | Linux arm64 gnu | ✓ | ✓ | ✓ | ✓ | 36 | | Linux arm64 musl | ✓ | ✓ | ✓ | ✓ | 37 | | Android arm64 | ✓ | ✓ | ✓ | ✓ | 38 | | Android armv7 | ✓ | ✓ | ✓ | ✓ | 39 | | FreeBSD x64 | ✓ | ✓ | ✓ | ✓ | 40 | 41 | ## API 42 | 43 | ```ts 44 | export function compressSync(input: Buffer | string | ArrayBuffer | Uint8Array): Buffer 45 | export function compress(input: Buffer | string | ArrayBuffer | Uint8Array): Promise 46 | export function uncompressSync(compressed: Buffer): Buffer 47 | export function uncompress(compressed: Buffer): Promise 48 | ``` 49 | 50 | ## Performance 51 | 52 | ### Hardware 53 | 54 | ``` 55 | OS: Windows 11 x86_64 56 | Host: Micro-Star International Co., Ltd. MS-7C35 57 | Kernel: 10.0.22000 58 | Terminal: Windows Terminal 59 | CPU: AMD Ryzen 9 5950X (32) @ 3.400GHz 60 | Memory: 32688MiB 61 | ``` 62 | 63 | ### Result 64 | 65 | ``` 66 | Running "Compress" suite... 67 | Progress: 100% 68 | 69 | snappy: 70 | 4 220 ops/s, ±0.66% | fastest 71 | 72 | snappy-v6: 73 | 2 018 ops/s, ±0.84% | 52.18% slower 74 | 75 | gzip: 76 | 233 ops/s, ±0.52% | slowest, 94.48% slower 77 | 78 | deflate: 79 | 235 ops/s, ±0.45% | 94.43% slower 80 | 81 | brotli: 82 | 7 ops/s, ±0.51% | slowest, 99.85% slower 83 | 84 | Finished 4 cases! 85 | Fastest: snappy 86 | Slowest: brotli 87 | 88 | Running "Decompress" suite... 89 | Progress: 100% 90 | 91 | snappy: 92 | 8 528 ops/s, ±1.03% | fastest 93 | 94 | snappy-v6: 95 | 6 357 ops/s, ±1.76% | 25.46% slower 96 | 97 | gzip: 98 | 1 406 ops/s, ±1.80% | slowest, 83.51% slower 99 | 100 | deflate: 101 | 1 435 ops/s, ±1.88% | 83.17% slower 102 | 103 | brotli: 104 | 1 208 ops/s, ±1.50% | slowest, 86.99% slower 105 | 106 | Finished 4 cases! 107 | Fastest: snappy 108 | Slowest: brotli 109 | ``` 110 | -------------------------------------------------------------------------------- /__test__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { compressSync, compress, uncompressSync, uncompress } from '../index' 4 | 5 | test('should be able to compress Buffer', (t) => { 6 | const fixture = 'hello world' 7 | t.deepEqual(compressSync(fixture), Buffer.from([11, 40, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])) 8 | }) 9 | 10 | test('should be able to compress Buffer with copied option', (t) => { 11 | const fixture = 'hello world' 12 | t.deepEqual( 13 | compressSync(fixture, { copyOutputData: true }), 14 | Buffer.from([11, 40, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), 15 | ) 16 | }) 17 | 18 | test('compress should be async version of compressSync', async (t) => { 19 | const fixture = 'hello world 😂 🎧 🚀' 20 | t.deepEqual(await compress(fixture), compressSync(fixture)) 21 | }) 22 | 23 | test('should be able to compress with copyOutputData', async (t) => { 24 | const fixture = 'hello world 😂 🎧 🚀' 25 | t.deepEqual(await compress(fixture, { copyOutputData: true }), compressSync(fixture)) 26 | }) 27 | 28 | test('should be able to uncompress sync', (t) => { 29 | const fixture = 'hello world 😂 🎧 🚀' 30 | t.deepEqual(uncompressSync(compressSync(fixture)), Buffer.from(fixture)) 31 | }) 32 | 33 | test('should be able to uncompress sync into string', (t) => { 34 | const fixture = 'hello world 😂 🎧 🚀' 35 | t.deepEqual(uncompressSync(compressSync(fixture), { asBuffer: false }), fixture) 36 | }) 37 | 38 | test('should be able to uncompress', async (t) => { 39 | const fixture = 'hello world 😂 🎧 🚀' 40 | t.deepEqual(await uncompress(await compress(fixture)), Buffer.from(fixture)) 41 | }) 42 | 43 | test('should be able to uncompress into string', async (t) => { 44 | const fixture = 'hello world 😂 🎧 🚀' 45 | t.deepEqual(await uncompress(await compress(fixture), { asBuffer: false }), fixture) 46 | }) 47 | -------------------------------------------------------------------------------- /__test__/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /benchmark/bench.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import { join } from 'node:path' 3 | import { promisify } from 'node:util' 4 | import { 5 | gzip, 6 | deflate, 7 | brotliCompress, 8 | inflate, 9 | brotliDecompress, 10 | gzipSync, 11 | deflateSync, 12 | brotliCompressSync, 13 | gunzip, 14 | } from 'node:zlib' 15 | 16 | import { Bench, hrtimeNow } from 'tinybench' 17 | import { compress as legacyCompress, uncompress as legacyUncompress } from 'legacy-snappy' 18 | 19 | import { compress, uncompress, compressSync } from '../index.js' 20 | import { fileURLToPath } from 'node:url' 21 | 22 | const gzipAsync = promisify(gzip) 23 | const brotliCompressAsync = promisify(brotliCompress) 24 | const deflateAsync = promisify(deflate) 25 | const gunzipAsync = promisify(gunzip) 26 | const inflateAsync = promisify(inflate) 27 | const brotliDecompressAsync = promisify(brotliDecompress) 28 | const compressV6 = promisify(legacyCompress) 29 | const uncompressV6 = promisify(legacyUncompress) 30 | 31 | const FIXTURE = readFileSync(join(fileURLToPath(import.meta.url), '..', '..', 'yarn.lock')) 32 | const SNAPPY_COMPRESSED_FIXTURE = Buffer.from(compressSync(FIXTURE)) 33 | const GZIP_FIXTURE = gzipSync(FIXTURE) 34 | const DEFLATED_FIXTURE = deflateSync(FIXTURE) 35 | const BROTLI_COMPRESSED_FIXTURE = brotliCompressSync(FIXTURE) 36 | 37 | const b = new Bench({ 38 | now: hrtimeNow, 39 | }) 40 | 41 | b.add('snappy', () => { 42 | return compress(FIXTURE) 43 | }) 44 | 45 | b.add('snappy-v6', () => { 46 | return compressV6(FIXTURE) 47 | }) 48 | 49 | b.add('gzip', () => { 50 | return gzipAsync(FIXTURE) 51 | }) 52 | 53 | b.add('deflate', () => { 54 | return deflateAsync(FIXTURE) 55 | }) 56 | 57 | b.add('brotli', () => { 58 | return brotliCompressAsync(FIXTURE) 59 | }) 60 | 61 | await b.run() 62 | 63 | console.table(b.table()) 64 | 65 | b.add('snappy', () => { 66 | return uncompress(SNAPPY_COMPRESSED_FIXTURE) 67 | }) 68 | 69 | b.add('snappy-v6', () => { 70 | // @ts-expect-error 71 | return uncompressV6(SNAPPY_COMPRESSED_FIXTURE) 72 | }) 73 | 74 | b.add('gzip', () => { 75 | return gunzipAsync(GZIP_FIXTURE) 76 | }) 77 | 78 | b.add('deflate', () => { 79 | return inflateAsync(DEFLATED_FIXTURE) 80 | }) 81 | 82 | b.add('brotli', () => { 83 | return brotliDecompressAsync(BROTLI_COMPRESSED_FIXTURE) 84 | }) 85 | 86 | await b.run() 87 | 88 | console.table(b.table()) 89 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /* auto-generated by NAPI-RS */ 2 | /* eslint-disable */ 3 | export declare function compress(input: string | Uint8Array, options?: EncOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise 4 | 5 | export declare function compressSync(input: string | Uint8Array, options?: EncOptions | undefined | null): Buffer 6 | 7 | export interface DecOptions { 8 | asBuffer?: boolean 9 | /** 10 | * do not use `create_external_buffer` to create the output buffer 11 | 12 | * set this option to `true` will make the API slower 13 | 14 | * for compatibility with electron >= 21 15 | 16 | * see https://www.electronjs.org/blog/v8-memory-cage and https://github.com/electron/electron/issues/35801#issuecomment-1261206333 17 | */ 18 | copyOutputData?: boolean 19 | } 20 | 21 | export interface EncOptions { 22 | /** 23 | * do not use `create_external_buffer` to create the output buffer 24 | 25 | * for compatibility with electron >= 21 26 | 27 | * set this option to `true` will make the API slower 28 | 29 | * see https://www.electronjs.org/blog/v8-memory-cage and https://github.com/electron/electron/issues/35801#issuecomment-1261206333 30 | */ 31 | copyOutputData?: boolean 32 | } 33 | 34 | export declare function uncompress(input: string | Uint8Array, options?: DecOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise 35 | 36 | export declare function uncompressSync(input: string | Uint8Array, options?: DecOptions | undefined | null): string | Buffer 37 | 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | /* eslint-disable */ 3 | /* auto-generated by NAPI-RS */ 4 | 5 | const { readFileSync } = require('fs') 6 | 7 | let nativeBinding = null 8 | const loadErrors = [] 9 | 10 | const isMusl = () => { 11 | let musl = false 12 | if (process.platform === 'linux') { 13 | musl = isMuslFromFilesystem() 14 | if (musl === null) { 15 | musl = isMuslFromReport() 16 | } 17 | if (musl === null) { 18 | musl = isMuslFromChildProcess() 19 | } 20 | } 21 | return musl 22 | } 23 | 24 | const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') 25 | 26 | const isMuslFromFilesystem = () => { 27 | try { 28 | return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') 29 | } catch { 30 | return null 31 | } 32 | } 33 | 34 | const isMuslFromReport = () => { 35 | const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null 36 | if (!report) { 37 | return null 38 | } 39 | if (report.header && report.header.glibcVersionRuntime) { 40 | return false 41 | } 42 | if (Array.isArray(report.sharedObjects)) { 43 | if (report.sharedObjects.some(isFileMusl)) { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | const isMuslFromChildProcess = () => { 51 | try { 52 | return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') 53 | } catch (e) { 54 | // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false 55 | return false 56 | } 57 | } 58 | 59 | function requireNative() { 60 | if (process.platform === 'android') { 61 | if (process.arch === 'arm64') { 62 | try { 63 | return require('./snappy.android-arm64.node') 64 | } catch (e) { 65 | loadErrors.push(e) 66 | } 67 | try { 68 | return require('snappy-android-arm64') 69 | } catch (e) { 70 | loadErrors.push(e) 71 | } 72 | 73 | } else if (process.arch === 'arm') { 74 | try { 75 | return require('./snappy.android-arm-eabi.node') 76 | } catch (e) { 77 | loadErrors.push(e) 78 | } 79 | try { 80 | return require('snappy-android-arm-eabi') 81 | } catch (e) { 82 | loadErrors.push(e) 83 | } 84 | 85 | } else { 86 | loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) 87 | } 88 | } else if (process.platform === 'win32') { 89 | if (process.arch === 'x64') { 90 | try { 91 | return require('./snappy.win32-x64-msvc.node') 92 | } catch (e) { 93 | loadErrors.push(e) 94 | } 95 | try { 96 | return require('snappy-win32-x64-msvc') 97 | } catch (e) { 98 | loadErrors.push(e) 99 | } 100 | 101 | } else if (process.arch === 'ia32') { 102 | try { 103 | return require('./snappy.win32-ia32-msvc.node') 104 | } catch (e) { 105 | loadErrors.push(e) 106 | } 107 | try { 108 | return require('snappy-win32-ia32-msvc') 109 | } catch (e) { 110 | loadErrors.push(e) 111 | } 112 | 113 | } else if (process.arch === 'arm64') { 114 | try { 115 | return require('./snappy.win32-arm64-msvc.node') 116 | } catch (e) { 117 | loadErrors.push(e) 118 | } 119 | try { 120 | return require('snappy-win32-arm64-msvc') 121 | } catch (e) { 122 | loadErrors.push(e) 123 | } 124 | 125 | } else { 126 | loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) 127 | } 128 | } else if (process.platform === 'darwin') { 129 | try { 130 | return require('./snappy.darwin-universal.node') 131 | } catch (e) { 132 | loadErrors.push(e) 133 | } 134 | try { 135 | return require('snappy-darwin-universal') 136 | } catch (e) { 137 | loadErrors.push(e) 138 | } 139 | 140 | if (process.arch === 'x64') { 141 | try { 142 | return require('./snappy.darwin-x64.node') 143 | } catch (e) { 144 | loadErrors.push(e) 145 | } 146 | try { 147 | return require('snappy-darwin-x64') 148 | } catch (e) { 149 | loadErrors.push(e) 150 | } 151 | 152 | } else if (process.arch === 'arm64') { 153 | try { 154 | return require('./snappy.darwin-arm64.node') 155 | } catch (e) { 156 | loadErrors.push(e) 157 | } 158 | try { 159 | return require('snappy-darwin-arm64') 160 | } catch (e) { 161 | loadErrors.push(e) 162 | } 163 | 164 | } else { 165 | loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) 166 | } 167 | } else if (process.platform === 'freebsd') { 168 | if (process.arch === 'x64') { 169 | try { 170 | return require('./snappy.freebsd-x64.node') 171 | } catch (e) { 172 | loadErrors.push(e) 173 | } 174 | try { 175 | return require('snappy-freebsd-x64') 176 | } catch (e) { 177 | loadErrors.push(e) 178 | } 179 | 180 | } else if (process.arch === 'arm64') { 181 | try { 182 | return require('./snappy.freebsd-arm64.node') 183 | } catch (e) { 184 | loadErrors.push(e) 185 | } 186 | try { 187 | return require('snappy-freebsd-arm64') 188 | } catch (e) { 189 | loadErrors.push(e) 190 | } 191 | 192 | } else { 193 | loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) 194 | } 195 | } else if (process.platform === 'linux') { 196 | if (process.arch === 'x64') { 197 | if (isMusl()) { 198 | try { 199 | return require('./snappy.linux-x64-musl.node') 200 | } catch (e) { 201 | loadErrors.push(e) 202 | } 203 | try { 204 | return require('snappy-linux-x64-musl') 205 | } catch (e) { 206 | loadErrors.push(e) 207 | } 208 | 209 | } else { 210 | try { 211 | return require('./snappy.linux-x64-gnu.node') 212 | } catch (e) { 213 | loadErrors.push(e) 214 | } 215 | try { 216 | return require('snappy-linux-x64-gnu') 217 | } catch (e) { 218 | loadErrors.push(e) 219 | } 220 | 221 | } 222 | } else if (process.arch === 'arm64') { 223 | if (isMusl()) { 224 | try { 225 | return require('./snappy.linux-arm64-musl.node') 226 | } catch (e) { 227 | loadErrors.push(e) 228 | } 229 | try { 230 | return require('snappy-linux-arm64-musl') 231 | } catch (e) { 232 | loadErrors.push(e) 233 | } 234 | 235 | } else { 236 | try { 237 | return require('./snappy.linux-arm64-gnu.node') 238 | } catch (e) { 239 | loadErrors.push(e) 240 | } 241 | try { 242 | return require('snappy-linux-arm64-gnu') 243 | } catch (e) { 244 | loadErrors.push(e) 245 | } 246 | 247 | } 248 | } else if (process.arch === 'arm') { 249 | if (isMusl()) { 250 | try { 251 | return require('./snappy.linux-arm-musleabihf.node') 252 | } catch (e) { 253 | loadErrors.push(e) 254 | } 255 | try { 256 | return require('snappy-linux-arm-musleabihf') 257 | } catch (e) { 258 | loadErrors.push(e) 259 | } 260 | 261 | } else { 262 | try { 263 | return require('./snappy.linux-arm-gnueabihf.node') 264 | } catch (e) { 265 | loadErrors.push(e) 266 | } 267 | try { 268 | return require('snappy-linux-arm-gnueabihf') 269 | } catch (e) { 270 | loadErrors.push(e) 271 | } 272 | 273 | } 274 | } else if (process.arch === 'riscv64') { 275 | if (isMusl()) { 276 | try { 277 | return require('./snappy.linux-riscv64-musl.node') 278 | } catch (e) { 279 | loadErrors.push(e) 280 | } 281 | try { 282 | return require('snappy-linux-riscv64-musl') 283 | } catch (e) { 284 | loadErrors.push(e) 285 | } 286 | 287 | } else { 288 | try { 289 | return require('./snappy.linux-riscv64-gnu.node') 290 | } catch (e) { 291 | loadErrors.push(e) 292 | } 293 | try { 294 | return require('snappy-linux-riscv64-gnu') 295 | } catch (e) { 296 | loadErrors.push(e) 297 | } 298 | 299 | } 300 | } else if (process.arch === 'ppc64') { 301 | try { 302 | return require('./snappy.linux-ppc64-gnu.node') 303 | } catch (e) { 304 | loadErrors.push(e) 305 | } 306 | try { 307 | return require('snappy-linux-ppc64-gnu') 308 | } catch (e) { 309 | loadErrors.push(e) 310 | } 311 | 312 | } else if (process.arch === 's390x') { 313 | try { 314 | return require('./snappy.linux-s390x-gnu.node') 315 | } catch (e) { 316 | loadErrors.push(e) 317 | } 318 | try { 319 | return require('snappy-linux-s390x-gnu') 320 | } catch (e) { 321 | loadErrors.push(e) 322 | } 323 | 324 | } else { 325 | loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) 326 | } 327 | } else { 328 | loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) 329 | } 330 | } 331 | 332 | nativeBinding = requireNative() 333 | 334 | if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { 335 | try { 336 | nativeBinding = require('./snappy.wasi.cjs') 337 | } catch (err) { 338 | if (process.env.NAPI_RS_FORCE_WASI) { 339 | loadErrors.push(err) 340 | } 341 | } 342 | if (!nativeBinding) { 343 | try { 344 | nativeBinding = require('snappy-wasm32-wasi') 345 | } catch (err) { 346 | if (process.env.NAPI_RS_FORCE_WASI) { 347 | loadErrors.push(err) 348 | } 349 | } 350 | } 351 | } 352 | 353 | if (!nativeBinding) { 354 | if (loadErrors.length > 0) { 355 | // TODO Link to documentation with potential fixes 356 | // - The package owner could build/publish bindings for this arch 357 | // - The user may need to bundle the correct files 358 | // - The user may need to re-install node_modules to get new packages 359 | throw new Error('Failed to load native binding', { cause: loadErrors }) 360 | } 361 | throw new Error(`Failed to load native binding`) 362 | } 363 | 364 | module.exports.compress = nativeBinding.compress 365 | module.exports.compressSync = nativeBinding.compressSync 366 | module.exports.uncompress = nativeBinding.uncompress 367 | module.exports.uncompressSync = nativeBinding.uncompressSync 368 | -------------------------------------------------------------------------------- /memory-leak-detect.mjs: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | 3 | import chalk from 'chalk' 4 | 5 | import { displayMemoryUsageFromNode } from './util.mjs' 6 | 7 | import { compress, uncompress } from './index.js' 8 | 9 | const initial = process.memoryUsage() 10 | 11 | async function detect(job) { 12 | for (let i = 0; i <= 100000; i++) { 13 | await job() 14 | if (i % 1000 === 0) { 15 | displayMemoryUsageFromNode(initial) 16 | } 17 | 18 | if (process.memoryUsage().rss - initial.rss >= 1024 * 1024 * 200) { 19 | throw new Error('Memory limit reached') 20 | } 21 | } 22 | } 23 | 24 | console.info(chalk.green('Decompress broken buffer...')) 25 | 26 | await detect(async () => uncompress(await fs.readFile('yarn.lock')).catch(() => {})) 27 | 28 | console.info(chalk.green('Compress file...')) 29 | 30 | await detect(async () => compress(await fs.readFile('yarn.lock'))) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snappy", 3 | "version": "7.2.2", 4 | "description": "Fastest Snappy compression library in Node.js", 5 | "main": "index.js", 6 | "repository": "https://github.com/Brooooooklyn/snappy", 7 | "license": "MIT", 8 | "keywords": [ 9 | "snappy", 10 | "snap", 11 | "compression", 12 | "compress", 13 | "napi-rs", 14 | "NAPI", 15 | "N-API", 16 | "Rust", 17 | "Node-API", 18 | "node-addon", 19 | "node-addon-api" 20 | ], 21 | "files": [ 22 | "index.d.ts", 23 | "index.js" 24 | ], 25 | "napi": { 26 | "binaryName": "snappy", 27 | "package": { 28 | "name": "@napi-rs/snappy" 29 | }, 30 | "targets": [ 31 | "x86_64-unknown-linux-gnu", 32 | "x86_64-apple-darwin", 33 | "x86_64-pc-windows-msvc", 34 | "x86_64-apple-darwin", 35 | "x86_64-pc-windows-msvc", 36 | "x86_64-unknown-linux-gnu", 37 | "x86_64-unknown-linux-musl", 38 | "aarch64-unknown-linux-gnu", 39 | "i686-pc-windows-msvc", 40 | "armv7-unknown-linux-gnueabihf", 41 | "aarch64-apple-darwin", 42 | "aarch64-linux-android", 43 | "arm-linux-androideabi", 44 | "x86_64-unknown-freebsd", 45 | "aarch64-unknown-linux-musl", 46 | "aarch64-pc-windows-msvc" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">= 10" 51 | }, 52 | "publishConfig": { 53 | "registry": "https://registry.npmjs.org/", 54 | "access": "public" 55 | }, 56 | "scripts": { 57 | "artifacts": "napi artifacts --output-dir artifacts --npm-dir ./npm", 58 | "bench": "node --import @swc-node/register/esm-register benchmark/bench.ts", 59 | "build": "napi build --platform --release", 60 | "build:debug": "napi build --platform", 61 | "format": "run-p format:source format:rs format:toml", 62 | "format:toml": "taplo format", 63 | "format:rs": "cargo fmt", 64 | "format:source": "prettier --config ./package.json --write .", 65 | "lint": "oxlint", 66 | "prepublishOnly": "napi prepublish -t npm && esbuild --minify --outfile=index.js --allow-overwrite index.js", 67 | "test": "ava", 68 | "test:mem": "node ./memory-leak-detect.mjs", 69 | "version": "napi version && conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md" 70 | }, 71 | "devDependencies": { 72 | "@napi-rs/cli": "^3.0.0-alpha.62", 73 | "@oxc-node/core": "^0.0.27", 74 | "@taplo/cli": "^0.7.0", 75 | "@types/node": "^22.4.2", 76 | "ava": "^6.1.3", 77 | "benny": "^3.7.1", 78 | "chalk": "^5.3.0", 79 | "conventional-changelog-cli": "^5.0.0", 80 | "esbuild": "^0.25.0", 81 | "husky": "^9.1.5", 82 | "legacy-snappy": "npm:snappy@6", 83 | "lint-staged": "^16.0.0", 84 | "npm-run-all2": "^8.0.0", 85 | "oxlint": "^0.17.0", 86 | "prettier": "^3.3.3", 87 | "pretty-bytes": "^7.0.0", 88 | "table": "^6.8.2", 89 | "tinybench": "^4.0.0", 90 | "typescript": "^5.5.4" 91 | }, 92 | "lint-staged": { 93 | "*.@(js|ts|tsx)": [ 94 | "eslint -c .eslintrc.yml --fix" 95 | ], 96 | "*.@(js|ts|tsx|yml|yaml|md|json)": [ 97 | "prettier --write" 98 | ] 99 | }, 100 | "ava": { 101 | "extensions": { 102 | "ts": "module" 103 | }, 104 | "nodeArguments": [ 105 | "--import", 106 | "@oxc-node/core/register" 107 | ], 108 | "timeout": "2m", 109 | "workerThreads": false, 110 | "environmentVariables": { 111 | "TS_NODE_PROJECT": "./tsconfig.json" 112 | } 113 | }, 114 | "prettier": { 115 | "printWidth": 120, 116 | "semi": false, 117 | "trailingComma": "all", 118 | "singleQuote": true, 119 | "arrowParens": "always" 120 | }, 121 | "funding": { 122 | "type": "github", 123 | "url": "https://github.com/sponsors/Brooooooklyn" 124 | }, 125 | "packageManager": "yarn@4.9.1", 126 | "exports": { 127 | ".": { 128 | "import": "./index.js", 129 | "require": "./index.js" 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":preserveSemverRanges"], 3 | "packageRules": [ 4 | { 5 | "automerge": true, 6 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"] 7 | } 8 | ], 9 | "lockFileMaintenance": { 10 | "enabled": true, 11 | "extends": ["schedule:monthly"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | #[macro_use] 4 | extern crate napi_derive; 5 | 6 | use napi::bindgen_prelude::*; 7 | use snap::raw::{Decoder, Encoder}; 8 | 9 | #[cfg(not(all(target_os = "linux", target_arch = "arm")))] 10 | #[global_allocator] 11 | static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; 12 | 13 | #[napi(object)] 14 | pub struct DecOptions { 15 | pub as_buffer: Option, 16 | /// do not use `create_external_buffer` to create the output buffer \n 17 | /// set this option to `true` will make the API slower \n 18 | /// for compatibility with electron >= 21 \n 19 | /// see https://www.electronjs.org/blog/v8-memory-cage and https://github.com/electron/electron/issues/35801#issuecomment-1261206333 20 | pub copy_output_data: Option, 21 | } 22 | 23 | #[napi(object)] 24 | pub struct EncOptions { 25 | /// do not use `create_external_buffer` to create the output buffer \n 26 | /// for compatibility with electron >= 21 \n 27 | /// set this option to `true` will make the API slower \n 28 | /// see https://www.electronjs.org/blog/v8-memory-cage and https://github.com/electron/electron/issues/35801#issuecomment-1261206333 29 | pub copy_output_data: Option, 30 | } 31 | 32 | pub struct Enc { 33 | inner: Encoder, 34 | data: Either, 35 | options: Option, 36 | } 37 | 38 | #[napi] 39 | impl Task for Enc { 40 | type Output = Vec; 41 | type JsValue = Buffer; 42 | 43 | fn compute(&mut self) -> Result { 44 | self 45 | .inner 46 | .compress_vec(match self.data { 47 | Either::A(ref b) => b.as_bytes(), 48 | Either::B(ref s) => s.as_ref(), 49 | }) 50 | .map_err(|e| Error::new(Status::GenericFailure, format!("{e}"))) 51 | } 52 | 53 | fn resolve(&mut self, env: Env, output: Self::Output) -> Result { 54 | if self 55 | .options 56 | .as_ref() 57 | .and_then(|o| o.copy_output_data) 58 | .unwrap_or(false) 59 | { 60 | BufferSlice::copy_from(&env, output) 61 | } else { 62 | BufferSlice::from_data(&env, output) 63 | } 64 | .and_then(|s| s.into_buffer(&env)) 65 | } 66 | } 67 | 68 | pub struct Dec { 69 | inner: Decoder, 70 | data: Either, 71 | options: Option, 72 | } 73 | 74 | #[napi] 75 | impl Task for Dec { 76 | type Output = Vec; 77 | type JsValue = Either; 78 | 79 | fn compute(&mut self) -> Result { 80 | self 81 | .inner 82 | .decompress_vec(match self.data { 83 | Either::A(ref s) => s.as_bytes(), 84 | Either::B(ref b) => b.as_ref(), 85 | }) 86 | .map_err(|e| Error::new(Status::GenericFailure, format!("{e}"))) 87 | } 88 | 89 | fn resolve(&mut self, env: Env, output: Self::Output) -> Result { 90 | let opt_ref = self.options.as_ref(); 91 | if opt_ref.and_then(|o| o.as_buffer).unwrap_or(true) { 92 | if opt_ref.and_then(|o| o.copy_output_data).unwrap_or(false) { 93 | BufferSlice::copy_from(&env, output) 94 | .and_then(|slice| slice.into_buffer(&env)) 95 | .map(Either::B) 96 | } else { 97 | BufferSlice::from_data(&env, output) 98 | .and_then(|slice| slice.into_buffer(&env)) 99 | .map(Either::B) 100 | } 101 | } else { 102 | Ok(Either::A(String::from_utf8(output).map_err(|e| { 103 | Error::new(Status::GenericFailure, format!("{e}")) 104 | })?)) 105 | } 106 | } 107 | } 108 | 109 | #[napi] 110 | pub fn compress_sync( 111 | env: Env, 112 | input: Either, 113 | options: Option, 114 | ) -> Result { 115 | let mut enc = Encoder::new(); 116 | enc 117 | .compress_vec(match input { 118 | Either::A(ref s) => s.as_bytes(), 119 | Either::B(b) => b, 120 | }) 121 | .map_err(|err| Error::new(Status::GenericFailure, format!("{err}"))) 122 | .and_then(|output| { 123 | if options 124 | .as_ref() 125 | .and_then(|o| o.copy_output_data) 126 | .unwrap_or(false) 127 | { 128 | BufferSlice::copy_from(&env, output) 129 | } else { 130 | BufferSlice::from_data(&env, output) 131 | } 132 | }) 133 | } 134 | 135 | #[napi] 136 | pub fn compress( 137 | input: Either, 138 | options: Option, 139 | signal: Option, 140 | ) -> Result> { 141 | let enc = Encoder::new(); 142 | let encoder = Enc { 143 | inner: enc, 144 | data: input, 145 | options, 146 | }; 147 | match signal { 148 | Some(s) => Ok(AsyncTask::with_signal(encoder, s)), 149 | None => Ok(AsyncTask::new(encoder)), 150 | } 151 | } 152 | 153 | #[napi] 154 | pub fn uncompress_sync( 155 | env: Env, 156 | input: Either, 157 | options: Option, 158 | ) -> Result> { 159 | let mut dec = Decoder::new(); 160 | dec 161 | .decompress_vec(match input { 162 | Either::A(ref s) => s.as_bytes(), 163 | Either::B(b) => b, 164 | }) 165 | .map_err(|err| Error::new(Status::GenericFailure, format!("{err}"))) 166 | .and_then(|output| { 167 | if options.as_ref().and_then(|o| o.as_buffer).unwrap_or(true) { 168 | if options 169 | .as_ref() 170 | .and_then(|o| o.copy_output_data) 171 | .unwrap_or(false) 172 | { 173 | BufferSlice::copy_from(&env, output).map(Either::B) 174 | } else { 175 | BufferSlice::from_data(&env, output).map(Either::B) 176 | } 177 | } else { 178 | Ok(Either::A(String::from_utf8(output).map_err(|e| { 179 | Error::new(Status::GenericFailure, format!("{e}")) 180 | })?)) 181 | } 182 | }) 183 | } 184 | 185 | #[napi] 186 | pub fn uncompress( 187 | input: Either, 188 | options: Option, 189 | signal: Option, 190 | ) -> Result> { 191 | let dec = Decoder::new(); 192 | let decoder = Dec { 193 | inner: dec, 194 | data: input, 195 | options, 196 | }; 197 | Ok(AsyncTask::with_optional_signal(decoder, signal)) 198 | } 199 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "strict": true, 5 | "moduleResolution": "node", 6 | "module": "ESNext", 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "esModuleInterop": true, 10 | "allowJs": true, 11 | "outDir": "dist", 12 | "allowSyntheticDefaultImports": true 13 | }, 14 | "include": ["./**/*.js", "./**/*.mjs", "./**/*.ts", "./**/*.tsx"], 15 | "files": ["index.js"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /util.mjs: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import prettyBytes from 'pretty-bytes' 3 | import { table } from 'table' 4 | 5 | export function displayMemoryUsageFromNode(initialMemoryUsage) { 6 | const finalMemoryUsage = process.memoryUsage() 7 | const titles = Object.keys(initialMemoryUsage).map((k) => chalk.whiteBright(k)) 8 | const tableData = [titles] 9 | const diffColumn = [] 10 | for (const [key, value] of Object.entries(initialMemoryUsage)) { 11 | const diff = finalMemoryUsage[key] - value 12 | const prettyDiff = prettyBytes(diff, { signed: true }) 13 | if (diff > 0) { 14 | diffColumn.push(chalk.red(prettyDiff)) 15 | } else if (diff < 0) { 16 | diffColumn.push(chalk.green(prettyDiff)) 17 | } else { 18 | diffColumn.push(chalk.grey(prettyDiff)) 19 | } 20 | } 21 | tableData.push(diffColumn) 22 | console.info(table(tableData)) 23 | } 24 | --------------------------------------------------------------------------------