zstdify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/dist/bitstream/bitReader.d.ts +26 -0
  4. package/dist/bitstream/bitReader.js +86 -0
  5. package/dist/bitstream/bitReader.js.map +1 -0
  6. package/dist/bitstream/bitReader.test.d.ts +1 -0
  7. package/dist/bitstream/bitReader.test.js +47 -0
  8. package/dist/bitstream/bitReader.test.js.map +1 -0
  9. package/dist/bitstream/bitReaderReverse.d.ts +23 -0
  10. package/dist/bitstream/bitReaderReverse.js +84 -0
  11. package/dist/bitstream/bitReaderReverse.js.map +1 -0
  12. package/dist/bitstream/bitReaderReverse.test.d.ts +1 -0
  13. package/dist/bitstream/bitReaderReverse.test.js +49 -0
  14. package/dist/bitstream/bitReaderReverse.test.js.map +1 -0
  15. package/dist/bitstream/bitWriter.d.ts +15 -0
  16. package/dist/bitstream/bitWriter.js +47 -0
  17. package/dist/bitstream/bitWriter.js.map +1 -0
  18. package/dist/bitstream/index.d.ts +4 -0
  19. package/dist/bitstream/index.js +5 -0
  20. package/dist/bitstream/index.js.map +1 -0
  21. package/dist/bitstream/littleEndian.d.ts +7 -0
  22. package/dist/bitstream/littleEndian.js +45 -0
  23. package/dist/bitstream/littleEndian.js.map +1 -0
  24. package/dist/bitstream/littleEndian.test.d.ts +1 -0
  25. package/dist/bitstream/littleEndian.test.js +21 -0
  26. package/dist/bitstream/littleEndian.test.js.map +1 -0
  27. package/dist/bitstream/varint.d.ts +9 -0
  28. package/dist/bitstream/varint.js +40 -0
  29. package/dist/bitstream/varint.js.map +1 -0
  30. package/dist/bitstream/varint.test.d.ts +1 -0
  31. package/dist/bitstream/varint.test.js +25 -0
  32. package/dist/bitstream/varint.test.js.map +1 -0
  33. package/dist/compress.d.ts +9 -0
  34. package/dist/compress.js +74 -0
  35. package/dist/compress.js.map +1 -0
  36. package/dist/decode/block.d.ts +11 -0
  37. package/dist/decode/block.js +28 -0
  38. package/dist/decode/block.js.map +1 -0
  39. package/dist/decode/block.test.d.ts +1 -0
  40. package/dist/decode/block.test.js +26 -0
  41. package/dist/decode/block.test.js.map +1 -0
  42. package/dist/decode/decompressFrame.d.ts +9 -0
  43. package/dist/decode/decompressFrame.js +147 -0
  44. package/dist/decode/decompressFrame.js.map +1 -0
  45. package/dist/decode/literals.corruption.test.d.ts +1 -0
  46. package/dist/decode/literals.corruption.test.js +26 -0
  47. package/dist/decode/literals.corruption.test.js.map +1 -0
  48. package/dist/decode/literals.d.ts +49 -0
  49. package/dist/decode/literals.js +300 -0
  50. package/dist/decode/literals.js.map +1 -0
  51. package/dist/decode/literals.test.d.ts +1 -0
  52. package/dist/decode/literals.test.js +52 -0
  53. package/dist/decode/literals.test.js.map +1 -0
  54. package/dist/decode/reconstruct.d.ts +13 -0
  55. package/dist/decode/reconstruct.js +80 -0
  56. package/dist/decode/reconstruct.js.map +1 -0
  57. package/dist/decode/reconstruct.test.d.ts +1 -0
  58. package/dist/decode/reconstruct.test.js +42 -0
  59. package/dist/decode/reconstruct.test.js.map +1 -0
  60. package/dist/decode/sequences.corruption.test.d.ts +1 -0
  61. package/dist/decode/sequences.corruption.test.js +32 -0
  62. package/dist/decode/sequences.corruption.test.js.map +1 -0
  63. package/dist/decode/sequences.d.ts +21 -0
  64. package/dist/decode/sequences.js +222 -0
  65. package/dist/decode/sequences.js.map +1 -0
  66. package/dist/decode/sequences.level1.test.d.ts +1 -0
  67. package/dist/decode/sequences.level1.test.js +35 -0
  68. package/dist/decode/sequences.level1.test.js.map +1 -0
  69. package/dist/decompress.d.ts +11 -0
  70. package/dist/decompress.js +59 -0
  71. package/dist/decompress.js.map +1 -0
  72. package/dist/dictionary/decoderDictionary.d.ts +15 -0
  73. package/dist/dictionary/decoderDictionary.js +116 -0
  74. package/dist/dictionary/decoderDictionary.js.map +1 -0
  75. package/dist/dictionary/decoderDictionary.test.d.ts +1 -0
  76. package/dist/dictionary/decoderDictionary.test.js +87 -0
  77. package/dist/dictionary/decoderDictionary.test.js.map +1 -0
  78. package/dist/encode/blockWriter.d.ts +5 -0
  79. package/dist/encode/blockWriter.js +27 -0
  80. package/dist/encode/blockWriter.js.map +1 -0
  81. package/dist/encode/blockWriter.test.d.ts +1 -0
  82. package/dist/encode/blockWriter.test.js +31 -0
  83. package/dist/encode/blockWriter.test.js.map +1 -0
  84. package/dist/encode/compressedBlock.d.ts +3 -0
  85. package/dist/encode/compressedBlock.js +449 -0
  86. package/dist/encode/compressedBlock.js.map +1 -0
  87. package/dist/encode/compressedBlock.test.d.ts +1 -0
  88. package/dist/encode/compressedBlock.test.js +63 -0
  89. package/dist/encode/compressedBlock.test.js.map +1 -0
  90. package/dist/encode/frameWriter.d.ts +4 -0
  91. package/dist/encode/frameWriter.js +34 -0
  92. package/dist/encode/frameWriter.js.map +1 -0
  93. package/dist/encode/frameWriter.test.d.ts +1 -0
  94. package/dist/encode/frameWriter.test.js +38 -0
  95. package/dist/encode/frameWriter.test.js.map +1 -0
  96. package/dist/encode/greedySequences.d.ts +7 -0
  97. package/dist/encode/greedySequences.js +82 -0
  98. package/dist/encode/greedySequences.js.map +1 -0
  99. package/dist/encode/greedySequences.test.d.ts +1 -0
  100. package/dist/encode/greedySequences.test.js +33 -0
  101. package/dist/encode/greedySequences.test.js.map +1 -0
  102. package/dist/entropy/fse.d.ts +33 -0
  103. package/dist/entropy/fse.js +217 -0
  104. package/dist/entropy/fse.js.map +1 -0
  105. package/dist/entropy/fse.test.d.ts +1 -0
  106. package/dist/entropy/fse.test.js +41 -0
  107. package/dist/entropy/fse.test.js.map +1 -0
  108. package/dist/entropy/huffman.d.ts +24 -0
  109. package/dist/entropy/huffman.js +70 -0
  110. package/dist/entropy/huffman.js.map +1 -0
  111. package/dist/entropy/huffman.test.d.ts +1 -0
  112. package/dist/entropy/huffman.test.js +22 -0
  113. package/dist/entropy/huffman.test.js.map +1 -0
  114. package/dist/entropy/index.d.ts +6 -0
  115. package/dist/entropy/index.js +5 -0
  116. package/dist/entropy/index.js.map +1 -0
  117. package/dist/entropy/predefined.d.ts +10 -0
  118. package/dist/entropy/predefined.js +18 -0
  119. package/dist/entropy/predefined.js.map +1 -0
  120. package/dist/entropy/weights.d.ts +20 -0
  121. package/dist/entropy/weights.js +108 -0
  122. package/dist/entropy/weights.js.map +1 -0
  123. package/dist/entropy/weights.test.d.ts +1 -0
  124. package/dist/entropy/weights.test.js +38 -0
  125. package/dist/entropy/weights.test.js.map +1 -0
  126. package/dist/errors.d.ts +7 -0
  127. package/dist/errors.js +13 -0
  128. package/dist/errors.js.map +1 -0
  129. package/dist/errors.test.d.ts +1 -0
  130. package/dist/errors.test.js +16 -0
  131. package/dist/errors.test.js.map +1 -0
  132. package/dist/frame/checksum.d.ts +17 -0
  133. package/dist/frame/checksum.js +94 -0
  134. package/dist/frame/checksum.js.map +1 -0
  135. package/dist/frame/checksum.test.d.ts +1 -0
  136. package/dist/frame/checksum.test.js +28 -0
  137. package/dist/frame/checksum.test.js.map +1 -0
  138. package/dist/frame/frameHeader.d.ts +26 -0
  139. package/dist/frame/frameHeader.js +127 -0
  140. package/dist/frame/frameHeader.js.map +1 -0
  141. package/dist/frame/frameHeader.test.d.ts +1 -0
  142. package/dist/frame/frameHeader.test.js +83 -0
  143. package/dist/frame/frameHeader.test.js.map +1 -0
  144. package/dist/frame/index.d.ts +4 -0
  145. package/dist/frame/index.js +4 -0
  146. package/dist/frame/index.js.map +1 -0
  147. package/dist/frame/skippable.d.ts +12 -0
  148. package/dist/frame/skippable.js +27 -0
  149. package/dist/frame/skippable.js.map +1 -0
  150. package/dist/frame/skippable.test.d.ts +1 -0
  151. package/dist/frame/skippable.test.js +35 -0
  152. package/dist/frame/skippable.test.js.map +1 -0
  153. package/dist/index.d.ts +2 -0
  154. package/dist/index.js +3 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/tsconfig.tsbuildinfo +1 -0
  157. package/package.json +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ben Houston
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.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # zstdify
2
+
3
+ [![NPM Package][npm]][npm-url]
4
+ [![NPM Downloads][npm-downloads]][npmtrends-url]
5
+ [![Tests][tests-badge]][tests-url]
6
+ [![Coverage][coverage-badge]][coverage-url]
7
+
8
+ Pure TypeScript zstd compression library. No native dependencies, works in Node.js and browsers.
9
+
10
+ ## Status
11
+
12
+ - **Decoder**: Raw and RLE blocks supported. Compressed blocks (Huffman/FSE) planned.
13
+ - **Encoder**: Raw baseline, RLE blocks for repeated-byte chunks (`level > 0`), plus an initial compressed-block path (`level > 1`) for selected single-sequence blocks with raw fallback.
14
+ - **Format**: RFC 8878 compliant for supported features.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { compress, decompress } from 'zstdify';
20
+
21
+ const data = new TextEncoder().encode('hello world');
22
+ const compressed = compress(data);
23
+ const restored = decompress(compressed);
24
+ // restored equals data
25
+ ```
26
+
27
+ ## API
28
+
29
+ - `compress(input: Uint8Array, options?: { level?: number }): Uint8Array`
30
+ - `decompress(input: Uint8Array, options?: { maxSize?: number }): Uint8Array`
31
+
32
+ ## CLI Tool
33
+
34
+ The **zstdify-cli** package is a command-line tool for compressing and decompressing files with zstd. Install from npm:
35
+
36
+ ```sh
37
+ pnpm add -g zstdify-cli
38
+ ```
39
+
40
+ ```bash
41
+ zstdify compress input.txt output.zst
42
+ zstdify extract output.zst restored.txt
43
+ ```
44
+
45
+ See [packages/cli/README.md](packages/cli/README.md) for full CLI documentation.
46
+
47
+ ## Development
48
+
49
+ ```bash
50
+ pnpm install
51
+ pnpm build
52
+ pnpm test
53
+ pnpm check
54
+ ```
55
+
56
+ ### How we validate
57
+
58
+ - **Round-trip**: `decompress(compress(x)) === x` for a variety of payloads and levels (see [packages/zstdify-tests/src/roundtrip/](packages/zstdify-tests/src/roundtrip/)), plus property-based tests with [fast-check](https://fast-check.dev/).
59
+ - **Conformance fixtures**: Pre-generated `.zst` files from the official zstd CLI; we decompress and compare. Legacy fixtures are documented in [packages/zstdify-tests/fixtures/README.md](packages/zstdify-tests/fixtures/README.md). A decodecorpus-style **corpus** (multiple sizes/levels, manifest with hashes) is generated by `pnpm --filter zstdify-tests run generate:corpus` and tested in `conformance/corpus-manifest.test.ts`.
60
+ - **Differential (zstd ↔ zstdify)**: With the zstd CLI installed, we test zstd compress → zstdify decompress and zstdify compress → zstd decompress across payloads and levels.
61
+ - **Corruption**: Truncation, checksum mismatch, invalid header bits, and related error paths are tested in [conformance/corruption.test.ts](packages/zstdify-tests/src/conformance/corruption.test.ts).
62
+ - **Compression regression**: `pnpm --filter zstdify-tests run regression:compression` checks that compressed sizes for fixed payloads do not increase (ratio stability).
63
+ - **Fuzzing**: A decompress harness (`pnpm --filter zstdify-tests run fuzz:decompress`) reads bytes from stdin and calls `decompress`; use with a fuzzer or corpus to find crashes. See [upstream zstd TESTING.md](https://github.com/facebook/zstd/blob/dev/TESTING.md) and decodecorpus for comparison.
64
+
65
+ ## Publishing
66
+
67
+ Publish the npm packages (library first, then CLI so it gets the correct `zstdify` version):
68
+
69
+ ```bash
70
+ pnpm make-release:zstdify
71
+ pnpm make-release:cli
72
+ ```
73
+
74
+ ## Project structure
75
+
76
+ - `packages/zstdify` - Core library
77
+ - `packages/cli` - CLI tool (`zstdify-cli` on npm)
78
+ - `packages/zstdify-tests` - Integration tests
79
+
80
+ # License
81
+
82
+ MIT
83
+
84
+ ## Author
85
+
86
+ [Ben Houston](https://benhouston3d.com), Sponsored by [Land of Assets](https://landofassets.com)
87
+
88
+ [npm]: https://img.shields.io/npm/v/zstdify
89
+ [npm-url]: https://www.npmjs.com/package/zstdify
90
+ [npm-downloads]: https://img.shields.io/npm/dw/zstdify
91
+ [npmtrends-url]: https://www.npmtrends.com/zstdify
92
+ [tests-badge]: https://github.com/bhouston/zstdify/workflows/Tests/badge.svg
93
+ [tests-url]: https://github.com/bhouston/zstdify/actions/workflows/test.yml
94
+ [coverage-badge]: https://codecov.io/gh/bhouston/zstdify/branch/main/graph/badge.svg
95
+ [coverage-url]: https://codecov.io/gh/bhouston/zstdify
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Little-endian bit reader with bounds-safe cursor.
3
+ * Reads bits LSB-first within bytes, bytes in forward order.
4
+ */
5
+ export declare class BitReader {
6
+ private data;
7
+ private byteOffset;
8
+ private bitOffset;
9
+ constructor(data: Uint8Array, byteOffset?: number);
10
+ /** Current byte position (after last fully consumed byte) */
11
+ get position(): number;
12
+ /** Total bits consumed */
13
+ get bitsConsumed(): number;
14
+ /** True if no more bits available */
15
+ get atEnd(): boolean;
16
+ /** Ensure at least n bits are available. Throws if not. */
17
+ ensure(n: number): void;
18
+ /** Read n bits (1-32), LSB first */
19
+ readBits(n: number): number;
20
+ /** Align to next byte boundary (skip remaining bits in current byte) */
21
+ align(): void;
22
+ /** Read a full byte (convenience, must be aligned or will read across boundary) */
23
+ readByte(): number;
24
+ /** Slice remaining bytes from current position (after aligning) */
25
+ readRemainingBytes(): Uint8Array;
26
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Little-endian bit reader with bounds-safe cursor.
3
+ * Reads bits LSB-first within bytes, bytes in forward order.
4
+ */
5
+ export class BitReader {
6
+ data;
7
+ byteOffset;
8
+ bitOffset; // 0-7, bits consumed in current byte
9
+ constructor(data, byteOffset = 0) {
10
+ this.data = data;
11
+ this.byteOffset = byteOffset;
12
+ this.bitOffset = 0;
13
+ }
14
+ /** Current byte position (after last fully consumed byte) */
15
+ get position() {
16
+ return this.byteOffset;
17
+ }
18
+ /** Total bits consumed */
19
+ get bitsConsumed() {
20
+ return this.byteOffset * 8 + this.bitOffset;
21
+ }
22
+ /** True if no more bits available */
23
+ get atEnd() {
24
+ return this.byteOffset >= this.data.length;
25
+ }
26
+ /** Ensure at least n bits are available. Throws if not. */
27
+ ensure(n) {
28
+ const bitsAvailable = (this.data.length - this.byteOffset) * 8 - this.bitOffset;
29
+ if (bitsAvailable < n) {
30
+ throw new RangeError(`BitReader: requested ${n} bits, only ${bitsAvailable} available`);
31
+ }
32
+ }
33
+ /** Read n bits (1-32), LSB first */
34
+ readBits(n) {
35
+ if (n < 1 || n > 32) {
36
+ throw new RangeError(`BitReader.readBits: n must be 1-32, got ${n}`);
37
+ }
38
+ this.ensure(n);
39
+ let value = 0;
40
+ let bitsLeft = n;
41
+ while (bitsLeft > 0) {
42
+ const byte = this.data[this.byteOffset] ?? 0;
43
+ const bitsInByte = 8 - this.bitOffset;
44
+ const take = Math.min(bitsLeft, bitsInByte);
45
+ const mask = (1 << take) - 1;
46
+ const shift = this.bitOffset;
47
+ value |= ((byte >> shift) & mask) << (n - bitsLeft);
48
+ this.bitOffset += take;
49
+ bitsLeft -= take;
50
+ if (this.bitOffset >= 8) {
51
+ this.byteOffset++;
52
+ this.bitOffset = 0;
53
+ }
54
+ }
55
+ return value;
56
+ }
57
+ /** Align to next byte boundary (skip remaining bits in current byte) */
58
+ align() {
59
+ if (this.bitOffset !== 0) {
60
+ this.bitOffset = 0;
61
+ this.byteOffset++;
62
+ }
63
+ }
64
+ /** Read a full byte (convenience, must be aligned or will read across boundary) */
65
+ readByte() {
66
+ if (this.bitOffset === 0) {
67
+ if (this.byteOffset >= this.data.length) {
68
+ throw new RangeError('BitReader: no more bytes');
69
+ }
70
+ const v = this.data[this.byteOffset++];
71
+ if (v === undefined)
72
+ throw new RangeError('BitReader: no more bytes');
73
+ return v;
74
+ }
75
+ return this.readBits(8);
76
+ }
77
+ /** Slice remaining bytes from current position (after aligning) */
78
+ readRemainingBytes() {
79
+ this.align();
80
+ if (this.byteOffset >= this.data.length) {
81
+ return new Uint8Array(0);
82
+ }
83
+ return this.data.subarray(this.byteOffset);
84
+ }
85
+ }
86
+ //# sourceMappingURL=bitReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitReader.js","sourceRoot":"","sources":["../../src/bitstream/bitReader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BitReader } from './bitReader.js';
3
+ describe('BitReader', () => {
4
+ it('reads single bits', () => {
5
+ // 0b10110100 = 0xB4
6
+ const data = new Uint8Array([0xb4]);
7
+ const r = new BitReader(data);
8
+ expect(r.readBits(1)).toBe(0);
9
+ expect(r.readBits(1)).toBe(0);
10
+ expect(r.readBits(1)).toBe(1);
11
+ expect(r.readBits(1)).toBe(0);
12
+ expect(r.readBits(1)).toBe(1);
13
+ expect(r.readBits(1)).toBe(1);
14
+ expect(r.readBits(1)).toBe(0);
15
+ expect(r.readBits(1)).toBe(1);
16
+ expect(r.atEnd).toBe(true);
17
+ });
18
+ it('reads multi-bit values', () => {
19
+ const data = new Uint8Array([0xff, 0x00]); // 11111111 00000000
20
+ const r = new BitReader(data);
21
+ expect(r.readBits(8)).toBe(0xff);
22
+ expect(r.readBits(8)).toBe(0x00);
23
+ });
24
+ it('reads across byte boundaries', () => {
25
+ // byte0: 1111 (low) 0000 (high), byte1: 1111 (low) 0000 (high)
26
+ const data = new Uint8Array([0b00001111, 0b00001111]);
27
+ const r = new BitReader(data);
28
+ expect(r.readBits(4)).toBe(0b1111); // low 4 of byte0
29
+ expect(r.readBits(8)).toBe(0b11110000); // high 4 of byte0 + low 4 of byte1
30
+ expect(r.readBits(4)).toBe(0); // high 4 of byte1
31
+ });
32
+ it('align works', () => {
33
+ const data = new Uint8Array([0xff, 0xab, 0xcd]);
34
+ const r = new BitReader(data);
35
+ r.readBits(3);
36
+ r.align();
37
+ expect(r.position).toBe(1);
38
+ expect(r.readByte()).toBe(0xab);
39
+ });
40
+ it('throws on out of bounds', () => {
41
+ const data = new Uint8Array([0xff]);
42
+ const r = new BitReader(data);
43
+ r.readBits(8);
44
+ expect(() => r.readBits(1)).toThrow();
45
+ });
46
+ });
47
+ //# sourceMappingURL=bitReader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitReader.test.js","sourceRoot":"","sources":["../../src/bitstream/bitReader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reverse (backward) bit reader for FSE/Huffman streams in zstd.
3
+ * Modeled after zstd's reverse stream semantics:
4
+ * - compute an absolute bit offset from stream start,
5
+ * - consume bits by decrementing that offset,
6
+ * - extract bits in little-endian bit order.
7
+ */
8
+ export declare class BitReaderReverse {
9
+ private readonly data;
10
+ private readonly startBit;
11
+ private readonly endBit;
12
+ private bitOffset;
13
+ constructor(data: Uint8Array, startByteOffset: number, lengthBytes: number, skipBitsAtStart?: number);
14
+ /** Read n bits (1-32), LSB first from current position (reading backward) */
15
+ readBits(n: number): number;
16
+ /** Skip trailing zero padding and end-mark bit from the stream tail. */
17
+ skipPadding(): void;
18
+ get position(): number;
19
+ /** Skip the first n bits at the logical start (the end of the buffer when reading backward). */
20
+ skipBitsAtEnd(n: number): void;
21
+ /** Undo a previous readBits() by pushing the cursor forward. */
22
+ unreadBits(n: number): void;
23
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Reverse (backward) bit reader for FSE/Huffman streams in zstd.
3
+ * Modeled after zstd's reverse stream semantics:
4
+ * - compute an absolute bit offset from stream start,
5
+ * - consume bits by decrementing that offset,
6
+ * - extract bits in little-endian bit order.
7
+ */
8
+ export class BitReaderReverse {
9
+ data;
10
+ startBit;
11
+ endBit;
12
+ bitOffset;
13
+ constructor(data, startByteOffset, lengthBytes, skipBitsAtStart = 0) {
14
+ if (lengthBytes < 0) {
15
+ throw new RangeError(`BitReaderReverse: negative length ${lengthBytes}`);
16
+ }
17
+ this.data = data;
18
+ this.startBit = startByteOffset * 8 + skipBitsAtStart;
19
+ this.endBit = (startByteOffset + lengthBytes) * 8;
20
+ this.bitOffset = this.endBit;
21
+ }
22
+ /** Read n bits (1-32), LSB first from current position (reading backward) */
23
+ readBits(n) {
24
+ if (n < 1 || n > 32) {
25
+ throw new RangeError(`BitReaderReverse.readBits: n must be 1-32, got ${n}`);
26
+ }
27
+ const requestedStart = this.bitOffset - n;
28
+ this.bitOffset = Math.max(this.startBit, requestedStart);
29
+ let value = 0;
30
+ for (let i = 0; i < n; i++) {
31
+ const absoluteBit = requestedStart + i;
32
+ if (absoluteBit < this.startBit) {
33
+ continue;
34
+ }
35
+ const byteIndex = absoluteBit >>> 3;
36
+ const bitInByte = absoluteBit & 7;
37
+ const bit = ((this.data[byteIndex] ?? 0) >>> bitInByte) & 1;
38
+ value |= bit << i;
39
+ }
40
+ return value;
41
+ }
42
+ /** Skip trailing zero padding and end-mark bit from the stream tail. */
43
+ skipPadding() {
44
+ if (this.endBit <= this.startBit) {
45
+ throw new RangeError('BitReaderReverse: empty stream');
46
+ }
47
+ const lastByteIndex = (this.endBit >>> 3) - 1;
48
+ const lastByte = this.data[lastByteIndex] ?? 0;
49
+ if (lastByte === 0) {
50
+ throw new RangeError('BitReaderReverse: invalid end marker');
51
+ }
52
+ const highestSetBit = 31 - Math.clz32(lastByte);
53
+ const paddingBits = 8 - highestSetBit; // includes end-mark + zero padding bits.
54
+ this.bitOffset = this.endBit - paddingBits;
55
+ if (this.bitOffset < this.startBit) {
56
+ throw new RangeError('BitReaderReverse: invalid padding');
57
+ }
58
+ }
59
+ get position() {
60
+ if (this.bitOffset <= this.startBit) {
61
+ return this.startBit >>> 3;
62
+ }
63
+ return (this.bitOffset - 1) >>> 3;
64
+ }
65
+ /** Skip the first n bits at the logical start (the end of the buffer when reading backward). */
66
+ skipBitsAtEnd(n) {
67
+ if (n <= 0)
68
+ return;
69
+ this.bitOffset -= n;
70
+ if (this.bitOffset < this.startBit) {
71
+ throw new RangeError('BitReaderReverse: buffer underflow');
72
+ }
73
+ }
74
+ /** Undo a previous readBits() by pushing the cursor forward. */
75
+ unreadBits(n) {
76
+ if (n <= 0)
77
+ return;
78
+ this.bitOffset += n;
79
+ if (this.bitOffset > this.endBit) {
80
+ throw new RangeError('BitReaderReverse: unread overflow');
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=bitReaderReverse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitReaderReverse.js","sourceRoot":"","sources":["../../src/bitstream/bitReaderReverse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BitReaderReverse } from './bitReaderReverse.js';
3
+ describe('BitReaderReverse', () => {
4
+ it('supports unreadBits to rollback over-read', () => {
5
+ const reader = new BitReaderReverse(new Uint8Array([0xa0, 0x01]), 0, 2);
6
+ reader.skipPadding();
7
+ const first = reader.readBits(2);
8
+ expect(first).toBe(2);
9
+ reader.unreadBits(1);
10
+ expect(reader.readBits(1)).toBe(0);
11
+ });
12
+ it('zero-fills when reading past stream start', () => {
13
+ const reader = new BitReaderReverse(new Uint8Array([0x80]), 0, 1);
14
+ reader.skipPadding();
15
+ // No payload bits remain after padding; reading still succeeds with zeros.
16
+ expect(reader.readBits(4)).toBe(0);
17
+ });
18
+ it('position returns start byte when bitOffset <= startBit', () => {
19
+ const reader = new BitReaderReverse(new Uint8Array([0x80]), 0, 1);
20
+ reader.skipPadding();
21
+ reader.readBits(4); // consume remaining (zeros)
22
+ expect(reader.position).toBe(0);
23
+ });
24
+ it('position returns correct byte after reading bits', () => {
25
+ const reader = new BitReaderReverse(new Uint8Array([0xa0, 0x01]), 0, 2);
26
+ reader.skipPadding();
27
+ reader.readBits(2);
28
+ expect(reader.position).toBeGreaterThanOrEqual(0);
29
+ expect(reader.position).toBeLessThanOrEqual(2);
30
+ });
31
+ it('skipBitsAtEnd advances logical position', () => {
32
+ const reader = new BitReaderReverse(new Uint8Array([0x80]), 0, 1);
33
+ reader.skipPadding();
34
+ reader.skipBitsAtEnd(1);
35
+ expect(reader.readBits(1)).toBe(0);
36
+ });
37
+ it('skipBitsAtEnd throws on buffer underflow', () => {
38
+ const reader = new BitReaderReverse(new Uint8Array([0x80]), 0, 1);
39
+ reader.skipPadding();
40
+ expect(() => reader.skipBitsAtEnd(100)).toThrow(/underflow|RangeError/i);
41
+ });
42
+ it('unreadBits throws on overflow', () => {
43
+ const reader = new BitReaderReverse(new Uint8Array([0xa0, 0x01]), 0, 2);
44
+ reader.skipPadding();
45
+ reader.readBits(2);
46
+ expect(() => reader.unreadBits(20)).toThrow(/overflow|RangeError/i);
47
+ });
48
+ });
49
+ //# sourceMappingURL=bitReaderReverse.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitReaderReverse.test.js","sourceRoot":"","sources":["../../src/bitstream/bitReaderReverse.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Little-endian bit writer.
3
+ * Writes bits LSB-first within bytes.
4
+ */
5
+ export declare class BitWriter {
6
+ private chunks;
7
+ private currentByte;
8
+ private bitOffset;
9
+ /** Write n bits (1-32), LSB first */
10
+ writeBits(n: number, bits: number): void;
11
+ /** Flush remaining bits to output. Call when done writing. */
12
+ flush(): Uint8Array;
13
+ /** Reset writer for reuse */
14
+ reset(): void;
15
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Little-endian bit writer.
3
+ * Writes bits LSB-first within bytes.
4
+ */
5
+ export class BitWriter {
6
+ chunks = [];
7
+ currentByte = 0;
8
+ bitOffset = 0; // 0-7, bits written in current byte
9
+ /** Write n bits (1-32), LSB first */
10
+ writeBits(n, bits) {
11
+ if (n < 1 || n > 32) {
12
+ throw new RangeError(`BitWriter.writeBits: n must be 1-32, got ${n}`);
13
+ }
14
+ const mask = n === 32 ? 0xffff_ffff : (1 << n) - 1;
15
+ let value = (bits >>> 0) & mask;
16
+ let bitsLeft = n;
17
+ while (bitsLeft > 0) {
18
+ const spaceInByte = 8 - this.bitOffset;
19
+ const take = Math.min(bitsLeft, spaceInByte);
20
+ const maskTake = (1 << take) - 1;
21
+ this.currentByte |= (value & maskTake) << this.bitOffset;
22
+ this.bitOffset += take;
23
+ bitsLeft -= take;
24
+ value >>= take;
25
+ if (this.bitOffset >= 8) {
26
+ this.chunks.push(this.currentByte & 0xff);
27
+ this.currentByte = 0;
28
+ this.bitOffset = 0;
29
+ }
30
+ }
31
+ }
32
+ /** Flush remaining bits to output. Call when done writing. */
33
+ flush() {
34
+ const result = [...this.chunks];
35
+ if (this.bitOffset > 0) {
36
+ result.push(this.currentByte & 0xff);
37
+ }
38
+ return new Uint8Array(result);
39
+ }
40
+ /** Reset writer for reuse */
41
+ reset() {
42
+ this.chunks = [];
43
+ this.currentByte = 0;
44
+ this.bitOffset = 0;
45
+ }
46
+ }
47
+ //# sourceMappingURL=bitWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitWriter.js","sourceRoot":"","sources":["../../src/bitstream/bitWriter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,SAAS;IACZ,MAAM,GAAa,EAAE,CAAC;IACtB,WAAW,GAAG,CAAC,CAAC;IAChB,SAAS,GAAG,CAAC,CAAC,CAAC,oCAAoC;IAE3D,qCAAqC;IACrC,SAAS,CAAC,CAAS,EAAE,IAAY,EAAQ;QACvC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,UAAU,CAAC,4CAA4C,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACnD,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;QAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,OAAO,QAAQ,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;YACvC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC7C,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC;YAEzD,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;YACvB,QAAQ,IAAI,IAAI,CAAC;YACjB,KAAK,KAAK,IAAI,CAAC;YAEf,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;gBACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;gBAC1C,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;gBACrB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IAAA,CACF;IAED,8DAA8D;IAC9D,KAAK,GAAe;QAClB,MAAM,MAAM,GAAa,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAAA,CAC/B;IAED,6BAA6B;IAC7B,KAAK,GAAS;QACZ,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;IAAA,CACpB;CACF"}
@@ -0,0 +1,4 @@
1
+ export { BitReader } from './bitReader.js';
2
+ export { BitWriter } from './bitWriter.js';
3
+ export { readU16LE, readU32LE, readU64LE } from './littleEndian.js';
4
+ export { decodeVarint, encodeVarint } from './varint.js';
@@ -0,0 +1,5 @@
1
+ export { BitReader } from './bitReader.js';
2
+ export { BitWriter } from './bitWriter.js';
3
+ export { readU16LE, readU32LE, readU64LE } from './littleEndian.js';
4
+ export { decodeVarint, encodeVarint } from './varint.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bitstream/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Little-endian read helpers for Uint8Array.
3
+ * All reads are bounds-checked.
4
+ */
5
+ export declare function readU16LE(data: Uint8Array, offset: number): number;
6
+ export declare function readU32LE(data: Uint8Array, offset: number): number;
7
+ export declare function readU64LE(data: Uint8Array, offset: number): bigint;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Little-endian read helpers for Uint8Array.
3
+ * All reads are bounds-checked.
4
+ */
5
+ export function readU16LE(data, offset) {
6
+ if (offset + 2 > data.length) {
7
+ throw new RangeError(`readU16LE: offset ${offset} + 2 exceeds length ${data.length}`);
8
+ }
9
+ const a = data[offset];
10
+ const b = data[offset + 1];
11
+ if (a === undefined || b === undefined)
12
+ throw new Error('unreachable');
13
+ return a | (b << 8);
14
+ }
15
+ export function readU32LE(data, offset) {
16
+ if (offset + 4 > data.length) {
17
+ throw new RangeError(`readU32LE: offset ${offset} + 4 exceeds length ${data.length}`);
18
+ }
19
+ const a = data[offset];
20
+ const b = data[offset + 1];
21
+ const c = data[offset + 2];
22
+ const d = data[offset + 3];
23
+ if (a === undefined || b === undefined || c === undefined || d === undefined)
24
+ throw new Error('unreachable');
25
+ return (a | (b << 8) | (c << 16) | (d << 24)) >>> 0;
26
+ }
27
+ export function readU64LE(data, offset) {
28
+ if (offset + 8 > data.length) {
29
+ throw new RangeError(`readU64LE: offset ${offset} + 8 exceeds length ${data.length}`);
30
+ }
31
+ const b0 = data[offset];
32
+ const b1 = data[offset + 1];
33
+ const b2 = data[offset + 2];
34
+ const b3 = data[offset + 3];
35
+ const b4 = data[offset + 4];
36
+ const b5 = data[offset + 5];
37
+ const b6 = data[offset + 6];
38
+ const b7 = data[offset + 7];
39
+ if ([b0, b1, b2, b3, b4, b5, b6, b7].some((x) => x === undefined))
40
+ throw new Error('unreachable');
41
+ const lo = (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
42
+ const hi = (b4 | (b5 << 8) | (b6 << 16) | (b7 << 24)) >>> 0;
43
+ return BigInt(lo) | (BigInt(hi) << 32n);
44
+ }
45
+ //# sourceMappingURL=littleEndian.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"littleEndian.js","sourceRoot":"","sources":["../../src/bitstream/littleEndian.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { readU16LE, readU32LE, readU64LE } from './littleEndian.js';
3
+ describe('littleEndian', () => {
4
+ it('readU16LE', () => {
5
+ const data = new Uint8Array([0x34, 0x12]); // 0x1234
6
+ expect(readU16LE(data, 0)).toBe(0x1234);
7
+ });
8
+ it('readU32LE', () => {
9
+ const data = new Uint8Array([0x78, 0x56, 0x34, 0x12]);
10
+ expect(readU32LE(data, 0)).toBe(0x12345678);
11
+ });
12
+ it('readU64LE', () => {
13
+ const data = new Uint8Array([0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01]);
14
+ expect(readU64LE(data, 0)).toBe(0x0123456789abcdefn);
15
+ });
16
+ it('throws on out of bounds', () => {
17
+ const data = new Uint8Array([1, 2, 3]);
18
+ expect(() => readU32LE(data, 0)).toThrow();
19
+ });
20
+ });
21
+ //# sourceMappingURL=littleEndian.test.js.map