├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── buildUMD.ts └── rewriteBuilds.ts ├── src └── index.ts ├── tests ├── README.md └── simple_cases_test.ts ├── tsconfig.esm.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | esm/ 4 | .DS_STORE 5 | umd/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | */ 2 | !esm/ 3 | !lib/ 4 | !umd/ 5 | tsconfig*.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.1 2 | - Fixed bugs with streaming API 3 | - Minor code quality improvements 4 | # 0.1.0 5 | - Stable release 6 | - Fixed TypeScript typings for ESM projects -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arjun Barrett 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fzstd 2 | High performance Zstandard decompression in a pure JavaScript, 8kB package 3 | 4 | ## Usage 5 | 6 | Import: 7 | ```js 8 | // I will assume that you use the following for the rest of this guide 9 | import * as fzstd from 'fzstd'; 10 | ``` 11 | 12 | If your environment doesn't support ES Modules (e.g. Node.js): 13 | ```js 14 | const fzstd = require('fzstd'); 15 | ``` 16 | 17 | If you want to load from a CDN in the browser: 18 | ```html 19 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | ``` 31 | 32 | If you are using Deno: 33 | ```js 34 | // Don't use the ?dts Skypack flag; it isn't necessary for Deno support 35 | // The @deno-types comment adds TypeScript typings 36 | 37 | // @deno-types="https://cdn.skypack.dev/fzstd@0.1.1/lib/index.d.ts" 38 | import * as fzstd from 'https://cdn.skypack.dev/fzstd@0.1.1?min'; 39 | ``` 40 | 41 | And use: 42 | ```js 43 | // This is an ArrayBuffer of data 44 | const compressedBuf = await fetch('/compressedData.zst').then( 45 | res => res.arrayBuffer() 46 | ); 47 | // To use fzstd, you need a Uint8Array 48 | const compressed = new Uint8Array(compressedBuf); 49 | // Note that Node.js Buffers work just fine as well: 50 | // const massiveFile = require('fs').readFileSync('aMassiveFile.txt'); 51 | 52 | const decompressed = fzstd.decompress(compressed); 53 | 54 | // Second argument is optional: custom output buffer 55 | const outBuf = new Uint8Array(100000); 56 | // IMPORTANT: fzstd will assume the buffer is sufficiently sized, so it 57 | // will yield corrupt data if the buffer is too small. It is highly 58 | // recommended to only specify this if you know the maximum output size. 59 | fzstd.decompress(compressed, outBuf); 60 | ``` 61 | 62 | You can also use data streams to minimize memory usage while decompressing. 63 | ```js 64 | let outChunks = []; 65 | const stream = new fzstd.Decompress((chunk, isLast) => { 66 | // Add to list of output chunks 67 | outChunks.push(chunk); 68 | // Log after all chunks decompressed 69 | if (isLast) { 70 | console.log('Output chunks:', outChunks); 71 | } 72 | }); 73 | 74 | // You can also attach the data handler separately if you don't want to 75 | // do so in the constructor. 76 | stream.ondata = (chunk, final) => { ... } 77 | 78 | // Since this is synchronous, all errors will be thrown by stream.push() 79 | stream.push(chunk1); 80 | stream.push(chunk2); 81 | ... 82 | // Last chunk must have the second parameter true 83 | stream.push(chunkLast, true); 84 | 85 | // Alternatively, you can push every data chunk normally and push an empty 86 | // chunk at the end: 87 | // stream.push(chunkLast); 88 | // stream.push(new Uint8Array(0), true); 89 | ``` 90 | 91 | ## Considerations 92 | Unlike my Zlib implementation [`fflate`](https://github.com/101arrowz/fflate), WebAssembly ports of Zstandard are usually significantly (30-40%) faster than `fzstd`. For very large decompression payloads (>100 MB), you'll usually want to use a WebAssembly port instead. However, `fzstd` has a few advantages. 93 | - Most WebAssembly ports do not support streaming, so they allocate a large amount of memory that cannot be freed. 94 | - Some WASM ports cannot operate without being provided the decompressed size of the data in advance. `fzstd` decides how much memory to allocate from the frame headers. 95 | - `fzstd` is absolutely tiny: at **8kB minified and 3.8kB after gzipping**, it's much smaller than most WASM implementations. 96 | 97 | Please note that unlike the reference implementation, `fzstd` only supports a maximum backreference distance of 225 bytes. If you need to decompress files with an "ultra" compression level (20 or greater) AND your files can be above 32MB decompressed, `fzstd` may fail to decompress properly. Consider using a WebAssembly port for files this large. -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fzstd", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "fzstd", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "^14.11.2", 13 | "terser": "^5.3.8", 14 | "ts-node": "^9.0.0", 15 | "typescript": "^4.1.5", 16 | "zstandard-wasm": "^1.5.0-rev.2" 17 | } 18 | }, 19 | "node_modules/@jridgewell/gen-mapping": { 20 | "version": "0.3.3", 21 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 22 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 23 | "dev": true, 24 | "dependencies": { 25 | "@jridgewell/set-array": "^1.0.1", 26 | "@jridgewell/sourcemap-codec": "^1.4.10", 27 | "@jridgewell/trace-mapping": "^0.3.9" 28 | }, 29 | "engines": { 30 | "node": ">=6.0.0" 31 | } 32 | }, 33 | "node_modules/@jridgewell/resolve-uri": { 34 | "version": "3.1.1", 35 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 36 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 37 | "dev": true, 38 | "engines": { 39 | "node": ">=6.0.0" 40 | } 41 | }, 42 | "node_modules/@jridgewell/set-array": { 43 | "version": "1.1.2", 44 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 45 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 46 | "dev": true, 47 | "engines": { 48 | "node": ">=6.0.0" 49 | } 50 | }, 51 | "node_modules/@jridgewell/source-map": { 52 | "version": "0.3.5", 53 | "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", 54 | "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", 55 | "dev": true, 56 | "dependencies": { 57 | "@jridgewell/gen-mapping": "^0.3.0", 58 | "@jridgewell/trace-mapping": "^0.3.9" 59 | } 60 | }, 61 | "node_modules/@jridgewell/sourcemap-codec": { 62 | "version": "1.4.15", 63 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 64 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 65 | "dev": true 66 | }, 67 | "node_modules/@jridgewell/trace-mapping": { 68 | "version": "0.3.19", 69 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", 70 | "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", 71 | "dev": true, 72 | "dependencies": { 73 | "@jridgewell/resolve-uri": "^3.1.0", 74 | "@jridgewell/sourcemap-codec": "^1.4.14" 75 | } 76 | }, 77 | "node_modules/@types/node": { 78 | "version": "14.18.63", 79 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", 80 | "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", 81 | "dev": true 82 | }, 83 | "node_modules/acorn": { 84 | "version": "8.10.0", 85 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", 86 | "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", 87 | "dev": true, 88 | "bin": { 89 | "acorn": "bin/acorn" 90 | }, 91 | "engines": { 92 | "node": ">=0.4.0" 93 | } 94 | }, 95 | "node_modules/arg": { 96 | "version": "4.1.3", 97 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 98 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 99 | "dev": true 100 | }, 101 | "node_modules/buffer-from": { 102 | "version": "1.1.2", 103 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 104 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 105 | "dev": true 106 | }, 107 | "node_modules/commander": { 108 | "version": "2.20.3", 109 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 110 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 111 | "dev": true 112 | }, 113 | "node_modules/create-require": { 114 | "version": "1.1.1", 115 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 116 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 117 | "dev": true 118 | }, 119 | "node_modules/decode-base64": { 120 | "version": "3.0.1", 121 | "resolved": "https://registry.npmjs.org/decode-base64/-/decode-base64-3.0.1.tgz", 122 | "integrity": "sha512-IWgiXlMAdm9c4RrOnvkFxYpfZRlOys4Wxnc/QT72hVLUZKCr7RPkfamgn2GXysCo06Zd4TGZyKaPHO4soBgSAg==", 123 | "dev": true, 124 | "dependencies": { 125 | "node-buffer-encoding": "^1.0.1" 126 | } 127 | }, 128 | "node_modules/diff": { 129 | "version": "4.0.2", 130 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 131 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 132 | "dev": true, 133 | "engines": { 134 | "node": ">=0.3.1" 135 | } 136 | }, 137 | "node_modules/make-error": { 138 | "version": "1.3.6", 139 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 140 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 141 | "dev": true 142 | }, 143 | "node_modules/node-buffer-encoding": { 144 | "version": "1.0.1", 145 | "resolved": "https://registry.npmjs.org/node-buffer-encoding/-/node-buffer-encoding-1.0.1.tgz", 146 | "integrity": "sha512-eklg9A4yXOlIZOIeV8D33gHZjw2g61TREuhucTM+/OR/xn4MXTZaV60fEYP+Lsa7C9bliJvvNrgkC6igafgKrw==", 147 | "dev": true 148 | }, 149 | "node_modules/once": { 150 | "name": "@fabiospampinato/once", 151 | "version": "2.0.0", 152 | "resolved": "https://registry.npmjs.org/@fabiospampinato/once/-/once-2.0.0.tgz", 153 | "integrity": "sha512-VJmruLTCTvb1+yYlGLupECCGAsz7bwB1ZRzx6AxdVpJ9YfXkWIRnoIWl4c3aWvPOWcgUQI2cqunTHjLCAZTrSA==", 154 | "dev": true 155 | }, 156 | "node_modules/source-map": { 157 | "version": "0.6.1", 158 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 159 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 160 | "dev": true, 161 | "engines": { 162 | "node": ">=0.10.0" 163 | } 164 | }, 165 | "node_modules/source-map-support": { 166 | "version": "0.5.21", 167 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 168 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 169 | "dev": true, 170 | "dependencies": { 171 | "buffer-from": "^1.0.0", 172 | "source-map": "^0.6.0" 173 | } 174 | }, 175 | "node_modules/terser": { 176 | "version": "5.21.0", 177 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", 178 | "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", 179 | "dev": true, 180 | "dependencies": { 181 | "@jridgewell/source-map": "^0.3.3", 182 | "acorn": "^8.8.2", 183 | "commander": "^2.20.0", 184 | "source-map-support": "~0.5.20" 185 | }, 186 | "bin": { 187 | "terser": "bin/terser" 188 | }, 189 | "engines": { 190 | "node": ">=10" 191 | } 192 | }, 193 | "node_modules/ts-node": { 194 | "version": "9.1.1", 195 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", 196 | "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", 197 | "dev": true, 198 | "dependencies": { 199 | "arg": "^4.1.0", 200 | "create-require": "^1.1.0", 201 | "diff": "^4.0.1", 202 | "make-error": "^1.1.1", 203 | "source-map-support": "^0.5.17", 204 | "yn": "3.1.1" 205 | }, 206 | "bin": { 207 | "ts-node": "dist/bin.js", 208 | "ts-node-script": "dist/bin-script.js", 209 | "ts-node-transpile-only": "dist/bin-transpile.js", 210 | "ts-script": "dist/bin-script-deprecated.js" 211 | }, 212 | "engines": { 213 | "node": ">=10.0.0" 214 | }, 215 | "peerDependencies": { 216 | "typescript": ">=2.7" 217 | } 218 | }, 219 | "node_modules/typescript": { 220 | "version": "4.9.5", 221 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 222 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 223 | "dev": true, 224 | "bin": { 225 | "tsc": "bin/tsc", 226 | "tsserver": "bin/tsserver" 227 | }, 228 | "engines": { 229 | "node": ">=4.2.0" 230 | } 231 | }, 232 | "node_modules/yn": { 233 | "version": "3.1.1", 234 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 235 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 236 | "dev": true, 237 | "engines": { 238 | "node": ">=6" 239 | } 240 | }, 241 | "node_modules/zstandard-wasm": { 242 | "version": "1.5.0-rev.5", 243 | "resolved": "https://registry.npmjs.org/zstandard-wasm/-/zstandard-wasm-1.5.0-rev.5.tgz", 244 | "integrity": "sha512-bNCV1ZwiU+zHjcR+N4P9FLxqurhLP6dhTivb7T6f86yIa9+gM3QlvQh+LfLRqj127agKcusSWuux46uM2J8fAg==", 245 | "dev": true, 246 | "dependencies": { 247 | "decode-base64": "^3.0.0", 248 | "once": "npm:@fabiospampinato/once@2.0.0" 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fzstd", 3 | "version": "0.1.1", 4 | "description": "High performance Zstandard (de)compression", 5 | "main": "./lib/index.js", 6 | "module": "./esm/index.mjs", 7 | "types": "./lib/index.d.ts", 8 | "unpkg": "./umd/index.js", 9 | "jsdelivr": "./umd/index.js", 10 | "exports": { 11 | ".": { 12 | "types": "./lib/index.d.ts", 13 | "import": "./esm/index.mjs", 14 | "require": "./lib/index.js" 15 | } 16 | }, 17 | "sideEffects": false, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/101arrowz/fzstd.git" 21 | }, 22 | "bugs": { 23 | "email": "arjunbarrett@gmail.com", 24 | "url": "https://github.com/101arrowz/fzstd/issues" 25 | }, 26 | "author": "Arjun Barrett", 27 | "license": "MIT", 28 | "keywords": [ 29 | "compression", 30 | "decompression", 31 | "zstd", 32 | "zstandard", 33 | "browser", 34 | "node.js", 35 | "tiny", 36 | "fast" 37 | ], 38 | "scripts": { 39 | "build": "npm run build:lib", 40 | "script": "node -r ts-node/register scripts/$SC.ts", 41 | "build:lib": "tsc && tsc --project tsconfig.esm.json && npm run build:rewrite && npm run build:umd", 42 | "build:umd": "SC=buildUMD npm run script", 43 | "build:rewrite": "SC=rewriteBuilds npm run script", 44 | "prepack": "npm run build" 45 | }, 46 | "devDependencies": { 47 | "@types/node": "^14.11.2", 48 | "terser": "^5.3.8", 49 | "ts-node": "^9.0.0", 50 | "typescript": "^4.1.5", 51 | "zstandard-wasm": "^1.5.0-rev.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/buildUMD.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; 2 | import { minify, MinifyOptions } from 'terser'; 3 | import { join } from 'path'; 4 | 5 | const p = (...fns: string[]) => join(__dirname, '..', ...fns); 6 | 7 | const src = readFileSync(p('lib', 'index.js'), 'utf8'); 8 | 9 | const opts: MinifyOptions = { 10 | mangle: { 11 | toplevel: true, 12 | }, 13 | compress: { 14 | passes: 5, 15 | unsafe: true, 16 | pure_getters: true 17 | }, 18 | sourceMap: false 19 | }; 20 | 21 | minify(src, opts).then(out => { 22 | const res = "!function(f){typeof module!='undefined'&&typeof exports=='object'?module.exports=f():typeof define!='undefined'&&define.amd?define(['fzstd',f]):(typeof self!='undefined'?self:this).fzstd=f()}(function(){var _e={};" + 23 | out.code!.replace(/exports\.(.*) = void 0;\n/, '').replace(/exports\./g, '_e.') + 'return _e})'; 24 | if (!existsSync(p('umd'))) mkdirSync(p('umd')); 25 | writeFileSync(p('umd', 'index.js'), res); 26 | }); -------------------------------------------------------------------------------- /scripts/rewriteBuilds.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync, unlinkSync } from 'fs'; 2 | import { join } from 'path'; 3 | const atClass = /\/\*\* \@class \*\//g, pure = '/*#__PURE__*/'; 4 | 5 | const libIndex = join(__dirname, '..', 'lib', 'index.js'); 6 | writeFileSync(libIndex, readFileSync(libIndex, 'utf-8') 7 | .replace(atClass, pure) 8 | .replace(/exports.__esModule = true;\n/, '') 9 | .replace(/exports\.(.*) = void 0;\n/, '') 10 | ); 11 | 12 | const esm = join(__dirname, '..', 'esm'); 13 | const esmIndex = join(esm, 'index.js'); 14 | writeFileSync(join(esm, 'index.mjs'), readFileSync(esmIndex, 'utf-8').replace(atClass, pure)); 15 | unlinkSync(esmIndex); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Some numerical data is initialized as -1 even when it doesn't need initialization to help the JIT infer types 2 | 3 | // aliases for shorter compressed code (most minifers don't do this) 4 | const ab = ArrayBuffer, u8 = Uint8Array, u16 = Uint16Array, i16 = Int16Array, u32 = Uint32Array, i32 = Int32Array; 5 | 6 | // Huffman decoding table 7 | interface HDT { 8 | // initial bits 9 | b: number; 10 | // symbols 11 | s: Uint8Array; 12 | // num bits 13 | n: Uint8Array; 14 | } 15 | 16 | // FSE decoding table 17 | interface FSEDT extends HDT { 18 | // next state 19 | t: Uint16Array; 20 | } 21 | 22 | // decompress Zstandard state 23 | interface DZstdState { 24 | // byte 25 | b: number; 26 | // out byte 27 | y: number; 28 | // dictionary ID 29 | d: number; 30 | // window 31 | w: Uint8Array; 32 | // max block size 33 | m: number; 34 | // uncompressed size 35 | u: number; 36 | // has checksum 37 | c: number; 38 | // offsets 39 | o: Int32Array; 40 | // window head 41 | e: number; 42 | // last huffman decoding table 43 | h?: HDT; 44 | // last FSE decoding tables 45 | t?: [FSEDT, FSEDT, FSEDT]; 46 | // last block 47 | l: number; 48 | } 49 | 50 | const slc = (v: Uint8Array, s: number, e?: number) => { 51 | if (u8.prototype.slice) return u8.prototype.slice.call(v, s, e); 52 | if (s == null || s < 0) s = 0; 53 | if (e == null || e > v.length) e = v.length; 54 | const n = new u8(e - s); 55 | n.set(v.subarray(s, e)); 56 | return n; 57 | }; 58 | 59 | const fill = (v: Uint8Array, n: number, s?: number, e?: number) => { 60 | if (u8.prototype.fill) return u8.prototype.fill.call(v, n, s, e); 61 | if (s == null || s < 0) s = 0; 62 | if (e == null || e > v.length) e = v.length; 63 | for (; s < e; ++s) v[s] = n; 64 | return v; 65 | }; 66 | 67 | const cpw = (v: Uint8Array, t: number, s?: number, e?: number) => { 68 | if (u8.prototype.copyWithin) return u8.prototype.copyWithin.call(v, t, s, e); 69 | if (s == null || s < 0) s = 0; 70 | if (e == null || e > v.length) e = v.length; 71 | while (s < e) { 72 | v[t++] = v[s++]; 73 | } 74 | }; 75 | 76 | /** 77 | * Codes for errors generated within this library 78 | */ 79 | export const ZstdErrorCode = { 80 | InvalidData: 0, 81 | WindowSizeTooLarge: 1, 82 | InvalidBlockType: 2, 83 | FSEAccuracyTooHigh: 3, 84 | DistanceTooFarBack: 4, 85 | UnexpectedEOF: 5 86 | } as const; 87 | 88 | type ZEC = (typeof ZstdErrorCode)[keyof typeof ZstdErrorCode]; 89 | 90 | // error codes 91 | const ec: Record = [ 92 | 'invalid zstd data', 93 | 'window size too large (>2046MB)', 94 | 'invalid block type', 95 | 'FSE accuracy too high', 96 | 'match distance too far back', 97 | 'unexpected EOF' 98 | ]; 99 | 100 | /** 101 | * An error generated within this library 102 | */ 103 | export interface ZstdError extends Error { 104 | /** 105 | * The code associated with this error 106 | */ 107 | code: ZEC; 108 | } 109 | 110 | const err = (ind: ZEC, msg?: string | 0, nt?: 1) => { 111 | const e: Partial = new Error(msg || ec[ind]); 112 | e.code = ind; 113 | if (Error.captureStackTrace) Error.captureStackTrace(e, err); 114 | if (!nt) throw e; 115 | return e as ZstdError; 116 | }; 117 | 118 | const rb = (d: Uint8Array, b: number, n: number) => { 119 | let i = 0, o = 0; 120 | for (; i < n; ++i) o |= d[b++] << (i << 3); 121 | return o; 122 | }; 123 | 124 | const b4 = (d: Uint8Array, b: number) => (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; 125 | 126 | // read Zstandard frame header 127 | const rzfh = (dat: Uint8Array, w?: Uint8Array | 1): number | DZstdState => { 128 | const n3 = dat[0] | (dat[1] << 8) | (dat[2] << 16); 129 | if (n3 == 0x2FB528 && dat[3] == 253) { 130 | // Zstandard 131 | const flg = dat[4]; 132 | // single segment checksum dict flag frame content flag 133 | const ss = (flg >> 5) & 1, cc = (flg >> 2) & 1, df = flg & 3, fcf = flg >> 6; 134 | if (flg & 8) err(0); 135 | // byte 136 | let bt = 6 - ss; 137 | // dict bytes 138 | const db = df == 3 ? 4 : df; 139 | // dictionary id 140 | const di = rb(dat, bt, db); 141 | bt += db; 142 | // frame size bytes 143 | const fsb = fcf ? (1 << fcf) : ss; 144 | // frame source size 145 | const fss = rb(dat, bt, fsb) + ((fcf == 1) && 256); 146 | // window size 147 | let ws = fss; 148 | if (!ss) { 149 | // window descriptor 150 | const wb = 1 << (10 + (dat[5] >> 3)); 151 | ws = wb + (wb >> 3) * (dat[5] & 7); 152 | } 153 | if (ws > 2145386496) err(1); 154 | const buf = new u8((w == 1 ? (fss || ws) : w ? 0 : ws) + 12); 155 | buf[0] = 1, buf[4] = 4, buf[8] = 8; 156 | return { 157 | b: bt + fsb, 158 | y: 0, 159 | l: 0, 160 | d: di, 161 | w: (w && w != 1) ? w : buf.subarray(12), 162 | e: ws, 163 | o: new i32(buf.buffer, 0, 3), 164 | u: fss, 165 | c: cc, 166 | m: Math.min(131072, ws) 167 | }; 168 | } else if (((n3 >> 4) | (dat[3] << 20)) == 0x184D2A5) { 169 | // skippable 170 | return b4(dat, 4) + 8; 171 | } 172 | err(0); 173 | }; 174 | 175 | // most significant bit for nonzero 176 | const msb = (val: number) => { 177 | let bits = 0; 178 | for (; (1 << bits) <= val; ++bits); 179 | return bits - 1; 180 | }; 181 | 182 | // read finite state entropy 183 | const rfse = (dat: Uint8Array, bt: number, mal: number): [number, FSEDT] => { 184 | // table pos 185 | let tpos = (bt << 3) + 4; 186 | // accuracy log 187 | const al = (dat[bt] & 15) + 5; 188 | if (al > mal) err(3); 189 | // size 190 | const sz = 1 << al; 191 | // probabilities symbols repeat index high threshold 192 | let probs = sz, sym = -1, re = -1, i = -1, ht = sz; 193 | // optimization: single allocation is much faster 194 | const buf = new ab(512 + (sz << 2)); 195 | const freq = new i16(buf, 0, 256); 196 | // same view as freq 197 | const dstate = new u16(buf, 0, 256); 198 | const nstate = new u16(buf, 512, sz); 199 | const bb1 = 512 + (sz << 1); 200 | const syms = new u8(buf, bb1, sz); 201 | const nbits = new u8(buf, bb1 + sz); 202 | while (sym < 255 && probs > 0) { 203 | const bits = msb(probs + 1); 204 | const cbt = tpos >> 3; 205 | // mask 206 | const msk = (1 << (bits + 1)) - 1; 207 | let val = ((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (tpos & 7)) & msk; 208 | // mask (1 fewer bit) 209 | const msk1fb = (1 << bits) - 1; 210 | // max small value 211 | const msv = msk - probs - 1; 212 | // small value 213 | const sval = val & msk1fb; 214 | if (sval < msv) tpos += bits, val = sval; 215 | else { 216 | tpos += bits + 1; 217 | if (val > msk1fb) val -= msv; 218 | } 219 | freq[++sym] = --val; 220 | if (val == -1) { 221 | probs += val; 222 | syms[--ht] = sym; 223 | } else probs -= val; 224 | if (!val) { 225 | do { 226 | // repeat byte 227 | const rbt = tpos >> 3; 228 | re = ((dat[rbt] | (dat[rbt + 1] << 8)) >> (tpos & 7)) & 3; 229 | tpos += 2; 230 | sym += re; 231 | } while (re == 3); 232 | } 233 | } 234 | if (sym > 255 || probs) err(0); 235 | let sympos = 0; 236 | // sym step (coprime with sz - formula from zstd source) 237 | const sstep = (sz >> 1) + (sz >> 3) + 3; 238 | // sym mask 239 | const smask = sz - 1; 240 | for (let s = 0; s <= sym; ++s) { 241 | const sf = freq[s]; 242 | if (sf < 1) { 243 | dstate[s] = -sf; 244 | continue; 245 | } 246 | // This is split into two loops in zstd to avoid branching, but as JS is higher-level that is unnecessary 247 | for (i = 0; i < sf; ++i) { 248 | syms[sympos] = s; 249 | do { 250 | sympos = (sympos + sstep) & smask; 251 | } while (sympos >= ht); 252 | } 253 | } 254 | // After spreading symbols, should be zero again 255 | if (sympos) err(0); 256 | for (i = 0; i < sz; ++i) { 257 | // next state 258 | const ns = dstate[syms[i]]++; 259 | // num bits 260 | const nb = nbits[i] = al - msb(ns); 261 | nstate[i] = (ns << nb) - sz; 262 | } 263 | return [(tpos + 7) >> 3, { 264 | b: al, 265 | s: syms, 266 | n: nbits, 267 | t: nstate 268 | }]; 269 | }; 270 | 271 | // read huffman 272 | const rhu = (dat: Uint8Array, bt: number): [number, HDT] => { 273 | // index weight count 274 | let i = 0, wc = -1; 275 | // buffer header byte 276 | const buf = new u8(292), hb = dat[bt]; 277 | // huffman weights 278 | const hw = buf.subarray(0, 256); 279 | // rank count 280 | const rc = buf.subarray(256, 268); 281 | // rank index 282 | const ri = new u16(buf.buffer, 268); 283 | // NOTE: at this point bt is 1 less than expected 284 | if (hb < 128) { 285 | // end byte, fse decode table 286 | const [ebt, fdt] = rfse(dat, bt + 1, 6); 287 | bt += hb; 288 | const epos = ebt << 3; 289 | // last byte 290 | const lb = dat[bt]; 291 | if (!lb) err(0); 292 | // state1 state2 state1 bits state2 bits 293 | let st1 = 0, st2 = 0, btr1 = fdt.b, btr2 = btr1; 294 | // fse pos 295 | // pre-increment to account for original deficit of 1 296 | let fpos = (++bt << 3) - 8 + msb(lb); 297 | for (;;) { 298 | fpos -= btr1; 299 | if (fpos < epos) break; 300 | let cbt = fpos >> 3; 301 | st1 += ((dat[cbt] | (dat[cbt + 1] << 8)) >> (fpos & 7)) & ((1 << btr1) - 1); 302 | hw[++wc] = fdt.s[st1]; 303 | fpos -= btr2; 304 | if (fpos < epos) break; 305 | cbt = fpos >> 3; 306 | st2 += ((dat[cbt] | (dat[cbt + 1] << 8)) >> (fpos & 7)) & ((1 << btr2) - 1); 307 | hw[++wc] = fdt.s[st2]; 308 | btr1 = fdt.n[st1]; 309 | st1 = fdt.t[st1]; 310 | btr2 = fdt.n[st2]; 311 | st2 = fdt.t[st2]; 312 | } 313 | if (++wc > 255) err(0); 314 | } else { 315 | wc = hb - 127; 316 | for (; i < wc; i += 2) { 317 | const byte = dat[++bt]; 318 | hw[i] = byte >> 4; 319 | hw[i + 1] = byte & 15; 320 | } 321 | ++bt; 322 | } 323 | // weight exponential sum 324 | let wes = 0; 325 | for (i = 0; i < wc; ++i) { 326 | const wt = hw[i]; 327 | // bits must be at most 11, same as weight 328 | if (wt > 11) err(0); 329 | wes += wt && (1 << (wt - 1)); 330 | } 331 | // max bits 332 | const mb = msb(wes) + 1; 333 | // table size 334 | const ts = 1 << mb; 335 | // remaining sum 336 | const rem = ts - wes; 337 | // must be power of 2 338 | if (rem & (rem - 1)) err(0); 339 | hw[wc++] = msb(rem) + 1; 340 | for (i = 0; i < wc; ++i) { 341 | const wt = hw[i]; 342 | ++rc[hw[i] = wt && (mb + 1 - wt)]; 343 | } 344 | // huf buf 345 | const hbuf = new u8(ts << 1); 346 | // symbols num bits 347 | const syms = hbuf.subarray(0, ts), nb = hbuf.subarray(ts); 348 | ri[mb] = 0; 349 | for (i = mb; i > 0; --i) { 350 | const pv = ri[i]; 351 | fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << (mb - i))); 352 | } 353 | if (ri[0] != ts) err(0); 354 | for (i = 0; i < wc; ++i) { 355 | const bits = hw[i]; 356 | if (bits) { 357 | const code = ri[bits]; 358 | fill(syms, i, code, ri[bits] = code + (1 << (mb - bits))); 359 | } 360 | } 361 | return [bt, { 362 | n: nb, 363 | b: mb, 364 | s: syms 365 | }]; 366 | }; 367 | 368 | // Tables generated using this: 369 | // https://gist.github.com/101arrowz/a979452d4355992cbf8f257cbffc9edd 370 | 371 | // default literal length table 372 | const dllt = /*#__PURE__*/ rfse(/*#__PURE__*/ new u8([ 373 | 81, 16, 99, 140, 49, 198, 24, 99, 12, 33, 196, 24, 99, 102, 102, 134, 70, 146, 4 374 | ]), 0, 6)[1]; 375 | 376 | // default match length table 377 | const dmlt = /*#__PURE__*/ rfse(/*#__PURE__*/ new u8([ 378 | 33, 20, 196, 24, 99, 140, 33, 132, 16, 66, 8, 33, 132, 16, 66, 8, 33, 68, 68, 68, 68, 68, 68, 68, 68, 36, 9 379 | ]), 0, 6)[1]; 380 | 381 | // default offset code table 382 | const doct = /*#__PURE__ */ rfse(/*#__PURE__*/ new u8([ 383 | 32, 132, 16, 66, 102, 70, 68, 68, 68, 68, 36, 73, 2 384 | ]), 0, 5)[1]; 385 | 386 | // bits to baseline 387 | const b2bl = (b: Uint8Array, s: number) => { 388 | const len = b.length, bl = new i32(len); 389 | for (let i = 0; i < len; ++i) { 390 | bl[i] = s; 391 | s += 1 << b[i]; 392 | } 393 | return bl; 394 | }; 395 | 396 | // literal length bits 397 | const llb = /*#__PURE__ */ new u8((/*#__PURE__ */ new i32([ 398 | 0, 0, 0, 0, 16843009, 50528770, 134678020, 202050057, 269422093 399 | ])).buffer, 0, 36); 400 | 401 | // literal length baseline 402 | const llbl = /*#__PURE__ */ b2bl(llb, 0); 403 | 404 | // match length bits 405 | const mlb = /*#__PURE__ */ new u8((/*#__PURE__ */ new i32([ 406 | 0, 0, 0, 0, 0, 0, 0, 0, 16843009, 50528770, 117769220, 185207048, 252579084, 16 407 | ])).buffer, 0, 53); 408 | 409 | // match length baseline 410 | const mlbl = /*#__PURE__ */ b2bl(mlb, 3); 411 | 412 | // decode huffman stream 413 | const dhu = (dat: Uint8Array, out: Uint8Array, hu: HDT) => { 414 | const len = dat.length, ss = out.length, lb = dat[len - 1], msk = (1 << hu.b) - 1, eb = -hu.b; 415 | if (!lb) err(0); 416 | let st = 0, btr = hu.b, pos = (len << 3) - 8 + msb(lb) - btr, i = -1; 417 | for (; pos > eb && i < ss;) { 418 | const cbt = pos >> 3; 419 | const val = (dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (pos & 7); 420 | st = ((st << btr) | val) & msk; 421 | out[++i] = hu.s[st]; 422 | pos -= (btr = hu.n[st]); 423 | } 424 | if (pos != eb || i + 1 != ss) err(0); 425 | }; 426 | 427 | // decode huffman stream 4x 428 | // TODO: use workers to parallelize 429 | const dhu4 = (dat: Uint8Array, out: Uint8Array, hu: HDT) => { 430 | let bt = 6; 431 | const ss = out.length, sz1 = (ss + 3) >> 2, sz2 = sz1 << 1, sz3 = sz1 + sz2; 432 | dhu(dat.subarray(bt, bt += dat[0] | (dat[1] << 8)), out.subarray(0, sz1), hu); 433 | dhu(dat.subarray(bt, bt += dat[2] | (dat[3] << 8)), out.subarray(sz1, sz2), hu); 434 | dhu(dat.subarray(bt, bt += dat[4] | (dat[5] << 8)), out.subarray(sz2, sz3), hu); 435 | dhu(dat.subarray(bt), out.subarray(sz3), hu); 436 | }; 437 | 438 | // read Zstandard block 439 | const rzb = (dat: Uint8Array, st: DZstdState, out?: Uint8Array) => { 440 | let bt = st.b; 441 | // byte 0 block type 442 | const b0 = dat[bt], btype = (b0 >> 1) & 3; 443 | st.l = b0 & 1; 444 | const sz = (b0 >> 3) | (dat[bt + 1] << 5) | (dat[bt + 2] << 13); 445 | // end byte for block 446 | const ebt = (bt += 3) + sz; 447 | if (btype == 1) { 448 | if (bt >= dat.length) return; 449 | st.b = bt + 1; 450 | if (out) { 451 | fill(out, dat[bt], st.y, st.y += sz); 452 | return out; 453 | } 454 | return fill(new u8(sz), dat[bt]); 455 | } 456 | if (ebt > dat.length) return; 457 | if (btype == 0) { 458 | st.b = ebt; 459 | if (out) { 460 | out.set(dat.subarray(bt, ebt), st.y); 461 | st.y += sz; 462 | return out; 463 | } 464 | return slc(dat, bt, ebt); 465 | } 466 | if (btype == 2) { 467 | // byte 3 lit btype size format 468 | const b3 = dat[bt], lbt = b3 & 3, sf = (b3 >> 2) & 3; 469 | // lit src size lit cmp sz 4 streams 470 | let lss = b3 >> 4, lcs = 0, s4 = 0; 471 | if (lbt < 2) { 472 | if (sf & 1) lss |= (dat[++bt] << 4) | ((sf & 2) && (dat[++bt] << 12)); 473 | else lss = b3 >> 3; 474 | } else { 475 | s4 = sf; 476 | if (sf < 2) lss |= ((dat[++bt] & 63) << 4), lcs = (dat[bt] >> 6) | (dat[++bt] << 2); 477 | else if (sf == 2) lss |= (dat[++bt] << 4) | ((dat[++bt] & 3) << 12), lcs = (dat[bt] >> 2) | (dat[++bt] << 6); 478 | else lss |= (dat[++bt] << 4) | ((dat[++bt] & 63) << 12), lcs = (dat[bt] >> 6) | (dat[++bt] << 2) | (dat[++bt] << 10); 479 | } 480 | ++bt; 481 | // add literals to end - can never overlap with backreferences because unused literals always appended 482 | let buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m); 483 | // starting point for literals 484 | let spl = buf.length - lss; 485 | if (lbt == 0) buf.set(dat.subarray(bt, bt += lss), spl); 486 | else if (lbt == 1) fill(buf, dat[bt++], spl); 487 | else { 488 | // huffman table 489 | let hu = st.h; 490 | if (lbt == 2) { 491 | const hud = rhu(dat, bt); 492 | // subtract description length 493 | lcs += bt - (bt = hud[0]); 494 | st.h = hu = hud[1]; 495 | } 496 | else if (!hu) err(0); 497 | (s4 ? dhu4 : dhu)(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu); 498 | } 499 | // num sequences 500 | let ns = dat[bt++]; 501 | if (ns) { 502 | if (ns == 255) ns = (dat[bt++] | (dat[bt++] << 8)) + 0x7F00; 503 | else if (ns > 127) ns = ((ns - 128) << 8) | dat[bt++]; 504 | // symbol compression modes 505 | const scm = dat[bt++]; 506 | if (scm & 3) err(0); 507 | const dts: [FSEDT, FSEDT, FSEDT] = [dmlt, doct, dllt]; 508 | for (let i = 2; i > -1; --i) { 509 | const md = (scm >> ((i << 1) + 2)) & 3; 510 | if (md == 1) { 511 | // rle buf 512 | const rbuf = new u8([0, 0, dat[bt++]]); 513 | dts[i] = { 514 | s: rbuf.subarray(2, 3), 515 | n: rbuf.subarray(0, 1), 516 | t: new u16(rbuf.buffer, 0, 1), 517 | b: 0 518 | }; 519 | } else if (md == 2) { 520 | // accuracy log 8 for offsets, 9 for others 521 | [bt, dts[i]] = rfse(dat, bt, 9 - (i & 1)); 522 | } else if (md == 3) { 523 | if (!st.t) err(0); 524 | dts[i] = st.t[i]; 525 | } 526 | } 527 | const [mlt, oct, llt] = st.t = dts; 528 | const lb = dat[ebt - 1]; 529 | if (!lb) err(0); 530 | let spos = (ebt << 3) - 8 + msb(lb) - llt.b, cbt = spos >> 3, oubt = 0; 531 | let lst = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << llt.b) - 1); 532 | cbt = (spos -= oct.b) >> 3; 533 | let ost = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << oct.b) - 1); 534 | cbt = (spos -= mlt.b) >> 3; 535 | let mst = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << mlt.b) - 1); 536 | for (++ns; --ns;) { 537 | const llc = llt.s[lst]; 538 | const lbtr = llt.n[lst]; 539 | const mlc = mlt.s[mst]; 540 | const mbtr = mlt.n[mst]; 541 | const ofc = oct.s[ost]; 542 | const obtr = oct.n[ost]; 543 | 544 | cbt = (spos -= ofc) >> 3; 545 | const ofp = 1 << ofc; 546 | let off = ofp + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16) | (dat[cbt + 3] << 24)) >>> (spos & 7)) & (ofp - 1)); 547 | cbt = (spos -= mlb[mlc]) >> 3; 548 | let ml = mlbl[mlc] + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (spos & 7)) & ((1 << mlb[mlc]) - 1)); 549 | cbt = (spos -= llb[llc]) >> 3; 550 | const ll = llbl[llc] + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (spos & 7)) & ((1 << llb[llc]) - 1)); 551 | 552 | cbt = (spos -= lbtr) >> 3; 553 | lst = llt.t[lst] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << lbtr) - 1)); 554 | cbt = (spos -= mbtr) >> 3; 555 | mst = mlt.t[mst] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << mbtr) - 1)); 556 | cbt = (spos -= obtr) >> 3; 557 | ost = oct.t[ost] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << obtr) - 1)); 558 | 559 | if (off > 3) { 560 | st.o[2] = st.o[1]; 561 | st.o[1] = st.o[0]; 562 | st.o[0] = off -= 3; 563 | } else { 564 | const idx = off - ((ll != 0) as unknown as number); 565 | if (idx) { 566 | off = idx == 3 ? st.o[0] - 1 : st.o[idx]; 567 | if (idx > 1) st.o[2] = st.o[1]; 568 | st.o[1] = st.o[0]; 569 | st.o[0] = off; 570 | } else off = st.o[0]; 571 | } 572 | for (let i = 0; i < ll; ++i) { 573 | buf[oubt + i] = buf[spl + i]; 574 | } 575 | oubt += ll, spl += ll; 576 | let stin = oubt - off; 577 | if (stin < 0) { 578 | let len = -stin; 579 | const bs = st.e + stin; 580 | if (len > ml) len = ml; 581 | for (let i = 0; i < len; ++i) { 582 | buf[oubt + i] = st.w[bs + i]; 583 | } 584 | oubt += len, ml -= len, stin = 0; 585 | } 586 | for (let i = 0; i < ml; ++i) { 587 | buf[oubt + i] = buf[stin + i]; 588 | } 589 | oubt += ml; 590 | } 591 | if (oubt != spl) { 592 | while (spl < buf.length) { 593 | buf[oubt++] = buf[spl++]; 594 | } 595 | } else oubt = buf.length; 596 | if (out) st.y += oubt; 597 | else buf = slc(buf, 0, oubt); 598 | } else if (out) { 599 | st.y += lss; 600 | if (spl) { 601 | for (let i = 0; i < lss; ++i) { 602 | buf[i] = buf[spl + i]; 603 | } 604 | } 605 | } else if (spl) buf = slc(buf, spl); 606 | st.b = ebt; 607 | return buf; 608 | } 609 | err(2); 610 | }; 611 | 612 | // concat 613 | const cct = (bufs: Uint8Array[], ol: number) => { 614 | if (bufs.length == 1) return bufs[0]; 615 | const buf = new u8(ol); 616 | for (let i = 0, b = 0; i < bufs.length; ++i) { 617 | const chk = bufs[i]; 618 | buf.set(chk, b); 619 | b += chk.length; 620 | } 621 | return buf; 622 | }; 623 | 624 | /** 625 | * Decompresses Zstandard data 626 | * @param dat The input data 627 | * @param buf The output buffer. If unspecified, the function will allocate 628 | * exactly enough memory to fit the decompressed data. If your 629 | * data has multiple frames and you know the output size, specifying 630 | * it will yield better performance. 631 | * @returns The decompressed data 632 | */ 633 | export function decompress(dat: Uint8Array, buf?: Uint8Array) { 634 | const bufs: Uint8Array[] = [], nb = +!buf as 0 | 1; 635 | let bt = 0, ol = 0; 636 | for (; dat.length;) { 637 | const st = rzfh(dat, nb || buf); 638 | if (typeof st == 'object') { 639 | if (nb) { 640 | buf = null; 641 | if (st.w.length == st.u) { 642 | bufs.push(buf = st.w); 643 | ol += st.u; 644 | } 645 | } else { 646 | bufs.push(buf); 647 | st.e = 0; 648 | } 649 | for (; !st.l;) { 650 | const blk = rzb(dat, st, buf); 651 | if (!blk) err(5); 652 | if (buf) st.e = st.y; 653 | else { 654 | bufs.push(blk); 655 | ol += blk.length; 656 | cpw(st.w, 0, blk.length); 657 | st.w.set(blk, st.w.length - blk.length); 658 | } 659 | } 660 | bt = st.b + (st.c * 4); 661 | } else bt = st; 662 | dat = dat.subarray(bt); 663 | } 664 | return cct(bufs, ol); 665 | } 666 | 667 | /** 668 | * Callback to handle data in Zstandard streams 669 | * @param data The data that was (de)compressed 670 | * @param final Whether this is the last chunk in the stream 671 | */ 672 | export type ZstdStreamHandler = (data: Uint8Array, final?: boolean) => unknown; 673 | 674 | /** 675 | * Decompressor for Zstandard streamed data 676 | */ 677 | export class Decompress { 678 | private s: DZstdState | number; 679 | private c: Uint8Array[]; 680 | private l: number; 681 | private z: number; 682 | /** 683 | * Creates a Zstandard decompressor 684 | * @param ondata The handler for stream data 685 | */ 686 | constructor(ondata?: ZstdStreamHandler) { 687 | this.ondata = ondata; 688 | this.c = []; 689 | this.l = 0; 690 | this.z = 0; 691 | } 692 | 693 | /** 694 | * Pushes data to be decompressed 695 | * @param chunk The chunk of data to push 696 | * @param final Whether or not this is the last chunk in the stream 697 | */ 698 | push(chunk: Uint8Array, final?: boolean) { 699 | if (typeof this.s == 'number') { 700 | const sub = Math.min(chunk.length, this.s as number); 701 | chunk = chunk.subarray(sub); 702 | (this.s as number) -= sub; 703 | } 704 | const sl = chunk.length; 705 | const ncs = sl + this.l; 706 | if (!this.s) { 707 | if (final) { 708 | if (!ncs) { 709 | this.ondata(new u8(0), true); 710 | return; 711 | } 712 | // min for frame + one block 713 | if (ncs < 5) err(5); 714 | } else if (ncs < 18) { 715 | this.c.push(chunk); 716 | this.l = ncs; 717 | return; 718 | } 719 | if (this.l) { 720 | this.c.push(chunk); 721 | chunk = cct(this.c, ncs); 722 | this.c = []; 723 | this.l = 0; 724 | } 725 | if (typeof (this.s = rzfh(chunk)) == 'number') return this.push(chunk, final); 726 | } 727 | if (typeof this.s != 'number') { 728 | if (ncs < (this.z || 3)) { 729 | if (final) err(5); 730 | this.c.push(chunk); 731 | this.l = ncs; 732 | return; 733 | } 734 | if (this.l) { 735 | this.c.push(chunk); 736 | chunk = cct(this.c, ncs); 737 | this.c = []; 738 | this.l = 0; 739 | } 740 | if (!this.z && ncs < (this.z = (chunk[(this.s as DZstdState).b] & 2) ? 4 : 3 + ((chunk[(this.s as DZstdState).b] >> 3) | (chunk[(this.s as DZstdState).b + 1] << 5) | (chunk[(this.s as DZstdState).b + 2] << 13)))) { 741 | if (final) err(5); 742 | this.c.push(chunk); 743 | this.l = ncs; 744 | return; 745 | } else this.z = 0; 746 | for (;;) { 747 | const blk = rzb(chunk, this.s as DZstdState); 748 | if (!blk) { 749 | if (final) err(5); 750 | const adc = chunk.subarray((this.s as DZstdState).b); 751 | (this.s as DZstdState).b = 0; 752 | this.c.push(adc), this.l += adc.length; 753 | return; 754 | } else { 755 | this.ondata(blk, false); 756 | cpw((this.s as DZstdState).w, 0, blk.length); 757 | (this.s as DZstdState).w.set(blk, (this.s as DZstdState).w.length - blk.length); 758 | } 759 | if ((this.s as DZstdState).l) { 760 | const rest = chunk.subarray((this.s as DZstdState).b); 761 | this.s = (this.s as DZstdState).c * 4; 762 | this.push(rest, final); 763 | return; 764 | } 765 | } 766 | } else if (final) err(5); 767 | } 768 | 769 | /** 770 | * Handler called whenever data is decompressed 771 | */ 772 | ondata: ZstdStreamHandler; 773 | } -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # fzstd Deno tests 2 | 3 | This folder contains test cases for fzstd that can be run with [Deno](https://deno.land/) 4 | 5 | ## How to execute tests 6 | 7 | ```bash 8 | deno test --no-check 9 | ``` 10 | -------------------------------------------------------------------------------- /tests/simple_cases_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; 2 | import * as fzstd from "../src/index.ts"; 3 | 4 | Deno.test("Decompression of 'Ok' text", () => { 5 | const data = [ 6 | 0x28, 0xB5, 0x2F, 0xFD, 0x24, 0x02, 0x11, 0x00, 0x00, 0x4F, 0x6B, 0x64, 0x50, 7 | 0xA9, 0x5A 8 | ]; 9 | const compressed = new Uint8Array(data); 10 | const decompressed = fzstd.decompress(compressed); 11 | const shouldBeOk = new TextDecoder().decode(decompressed); 12 | assertEquals("Ok", shouldBeOk); 13 | }); 14 | 15 | Deno.test("Decompression of Lorem ipsum text", () => { 16 | const data = [ 17 | 0x28, 0xB5, 0x2F, 0xFD, 0x24, 0x84, 0xFD, 0x02, 0x00, 0x92, 0x07, 0x15, 18 | 0x13, 0x90, 0x07, 0x0C, 0xC9, 0x6E, 0xBB, 0x5B, 0x93, 0x4C, 0x3E, 0x07, 19 | 0xB5, 0xE6, 0x59, 0xF1, 0x89, 0x6B, 0x52, 0xE9, 0x83, 0xDA, 0x40, 0xF8, 20 | 0x2D, 0xED, 0x1E, 0xA9, 0xCB, 0x01, 0x73, 0x2E, 0x97, 0x5D, 0xB9, 0x13, 21 | 0x5B, 0x66, 0x79, 0xAF, 0x81, 0x66, 0xE0, 0x43, 0x50, 0xFB, 0x47, 0xFB, 22 | 0x21, 0xFC, 0x89, 0x59, 0x0B, 0x3E, 0xB8, 0x8C, 0x4E, 0xC0, 0x9A, 0xAD, 23 | 0x42, 0x15, 0x72, 0x6D, 0x26, 0x1E, 0x5A, 0xCC, 0x39, 0xC1, 0x74, 0x72, 24 | 0xAE, 0xBD, 0xA6, 0x65, 0xF1, 0xEB, 0x2D, 0xD4, 0x8F, 0x34, 0x01, 0x01, 25 | 0x02, 0x00, 0x3E, 0x53, 0x53, 0xB3, 0xE6, 0x19, 0xB0, 0x58, 0x5B, 0x26 26 | ]; 27 | const compressed = new Uint8Array(data); 28 | const decompressed = fzstd.decompress(compressed); 29 | const shouldBeLoremIpsum = new TextDecoder().decode(decompressed); 30 | assertEquals("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec sem urna. Morbi mollis, massa a convallis iaculis, mauris neque.", shouldBeLoremIpsum); 31 | }); 32 | 33 | Deno.test("Decompression of 1 000 000 nuls", () => { 34 | const data = [ 35 | 0x28, 0xB5, 0x2F, 0xFD, 0xA4, 0x40, 0x42, 0x0F, 0x00, 0x54, 0x00, 0x00, 36 | 0x10, 0x00, 0x00, 0x01, 0x00, 0xFB, 0xFF, 0x39, 0xC0, 0x02, 0x02, 0x00, 37 | 0x10, 0x00, 0x02, 0x00, 0x10, 0x00, 0x02, 0x00, 0x10, 0x00, 0x02, 0x00, 38 | 0x10, 0x00, 0x02, 0x00, 0x10, 0x00, 0x02, 0x00, 0x10, 0x00, 0x03, 0x12, 39 | 0x0A, 0x00, 0xCC, 0xAE, 0xCA, 0x39 40 | ]; 41 | const compressed = new Uint8Array(data); 42 | const decompressed : Uint8Array = fzstd.decompress(compressed); 43 | const arraySum = decompressed.reduce((accumulator, currentValue) => accumulator + currentValue); 44 | assertEquals(1_000_000, decompressed.length); 45 | assertEquals(0, arraySum); 46 | }); -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "module": "ESNext", 6 | "outDir": "esm" 7 | } 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "moduleResolution": "node", 5 | "outDir": "lib/" 6 | }, 7 | "include": ["src/*.ts"] 8 | } --------------------------------------------------------------------------------