├── .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 | }
--------------------------------------------------------------------------------