@btoon/js 0.0.0-stage → 0.1.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -2
  3. package/dist/cjs/constants.d.ts +34 -0
  4. package/dist/cjs/constants.js +32 -0
  5. package/dist/cjs/core.d.ts +15 -0
  6. package/dist/cjs/core.js +275 -0
  7. package/dist/cjs/decode.d.ts +10 -0
  8. package/dist/cjs/decode.js +554 -0
  9. package/dist/cjs/encode.d.ts +2 -0
  10. package/dist/cjs/encode.js +946 -0
  11. package/dist/cjs/errors.d.ts +53 -0
  12. package/dist/cjs/errors.js +45 -0
  13. package/dist/cjs/index.d.ts +68 -0
  14. package/dist/cjs/index.js +86 -0
  15. package/dist/cjs/inflate.d.ts +9 -0
  16. package/dist/cjs/inflate.js +353 -0
  17. package/dist/cjs/node.d.ts +64 -0
  18. package/dist/cjs/node.js +82 -0
  19. package/dist/cjs/options.d.ts +95 -0
  20. package/dist/cjs/options.js +128 -0
  21. package/dist/cjs/package.json +3 -0
  22. package/dist/cjs/platform-node.d.ts +3 -0
  23. package/dist/cjs/platform-node.js +132 -0
  24. package/dist/cjs/platform-portable.d.ts +7 -0
  25. package/dist/cjs/platform-portable.js +16 -0
  26. package/dist/cjs/sha256.d.ts +5 -0
  27. package/dist/cjs/sha256.js +141 -0
  28. package/dist/cjs/utf8.d.ts +19 -0
  29. package/dist/cjs/utf8.js +107 -0
  30. package/dist/cjs/values.d.ts +58 -0
  31. package/dist/cjs/values.js +129 -0
  32. package/dist/cjs/web.d.ts +25 -0
  33. package/dist/cjs/web.js +151 -0
  34. package/dist/esm/constants.d.ts +34 -0
  35. package/dist/esm/constants.js +29 -0
  36. package/dist/esm/core.d.ts +15 -0
  37. package/dist/esm/core.js +269 -0
  38. package/dist/esm/decode.d.ts +10 -0
  39. package/dist/esm/decode.js +549 -0
  40. package/dist/esm/encode.d.ts +2 -0
  41. package/dist/esm/encode.js +943 -0
  42. package/dist/esm/errors.d.ts +53 -0
  43. package/dist/esm/errors.js +39 -0
  44. package/dist/esm/index.d.ts +68 -0
  45. package/dist/esm/index.js +74 -0
  46. package/dist/esm/inflate.d.ts +9 -0
  47. package/dist/esm/inflate.js +349 -0
  48. package/dist/esm/node.d.ts +64 -0
  49. package/dist/esm/node.js +70 -0
  50. package/dist/esm/options.d.ts +95 -0
  51. package/dist/esm/options.js +124 -0
  52. package/dist/esm/platform-node.d.ts +3 -0
  53. package/dist/esm/platform-node.js +96 -0
  54. package/dist/esm/platform-portable.d.ts +7 -0
  55. package/dist/esm/platform-portable.js +13 -0
  56. package/dist/esm/sha256.d.ts +5 -0
  57. package/dist/esm/sha256.js +136 -0
  58. package/dist/esm/utf8.d.ts +19 -0
  59. package/dist/esm/utf8.js +99 -0
  60. package/dist/esm/values.d.ts +58 -0
  61. package/dist/esm/values.js +120 -0
  62. package/dist/esm/web.d.ts +25 -0
  63. package/dist/esm/web.js +143 -0
  64. package/package.json +85 -4
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Machine-readable error codes. All but `invalid_argument` are the decoder error codes
3
+ * of SPEC §7; `invalid_argument` reports a value or option the library cannot accept.
4
+ */
5
+ export type BtoonErrorCode =
6
+ /** Input ends before a value is complete, or a length runs past the input. */
7
+ 'truncated'
8
+ /** The reserved marker byte `0xc1`. */
9
+ | 'invalid_marker'
10
+ /** Bytes remain after the top-level value (strict mode). */
11
+ | 'trailing_data'
12
+ /** A string, map key or column name is not well-formed UTF-8 (or, when encoding, a string contains a lone surrogate). */
13
+ | 'invalid_utf8'
14
+ /** A map key is not a string. */
15
+ | 'non_string_key'
16
+ /** A map contains the same key twice (strict mode). */
17
+ | 'duplicate_key'
18
+ /** Nesting deeper than `maxDepth`, or a cyclic value. */
19
+ | 'max_depth'
20
+ /** A malformed BTOON extension payload (timestamp, date, datetime, bigint, vectors). */
21
+ | 'invalid_extension'
22
+ /** A malformed tabular payload. */
23
+ | 'invalid_tabular'
24
+ /** A malformed compression or signed envelope, or a corrupt compressed payload. */
25
+ | 'invalid_envelope'
26
+ /** A size above a configured limit (for example `maxDecompressedSize`). */
27
+ | 'size_limit'
28
+ /** A compression algorithm that is unknown or not available in this environment. */
29
+ | 'unsupported_compression'
30
+ /** HMAC verification failed, a signature was required but missing, or a signed input arrived without a key. */
31
+ | 'authentication_failed'
32
+ /** A value that cannot be encoded, or an invalid option. */
33
+ | 'invalid_argument';
34
+ /**
35
+ * The only error type thrown by this library's `encode`/`decode` functions (and the only
36
+ * rejection reason of the async variants). Branch on {@link BtoonError.code}, which is
37
+ * stable across BTOON implementations; the message is for humans and may change.
38
+ */
39
+ export declare class BtoonError extends Error {
40
+ /** Machine-readable error code (SPEC §7, plus `invalid_argument`). */
41
+ readonly code: BtoonErrorCode;
42
+ constructor(code: BtoonErrorCode, message: string, options?: {
43
+ cause?: unknown;
44
+ });
45
+ }
46
+ /** Shorthand used internally: `throw fail('truncated', '...')`. */
47
+ export declare function fail(code: BtoonErrorCode, message: string, cause?: unknown): BtoonError;
48
+ /**
49
+ * True for the engine's "too much recursion" error. Raising `maxDepth` far above the
50
+ * default can exhaust the JavaScript call stack before the depth limit triggers; that
51
+ * case is reported as `max_depth` instead of leaking a RangeError.
52
+ */
53
+ export declare function isStackOverflow(e: unknown): boolean;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The only error type thrown by this library's `encode`/`decode` functions (and the only
3
+ * rejection reason of the async variants). Branch on {@link BtoonError.code}, which is
4
+ * stable across BTOON implementations; the message is for humans and may change.
5
+ */
6
+ export class BtoonError extends Error {
7
+ constructor(code, message, options) {
8
+ super(message);
9
+ this.name = 'BtoonError';
10
+ this.code = code;
11
+ if (options !== undefined && 'cause' in options) {
12
+ Object.defineProperty(this, 'cause', {
13
+ value: options.cause,
14
+ writable: true,
15
+ enumerable: false,
16
+ configurable: true,
17
+ });
18
+ }
19
+ }
20
+ }
21
+ /** Shorthand used internally: `throw fail('truncated', '...')`. */
22
+ export function fail(code, message, cause) {
23
+ return cause === undefined ? new BtoonError(code, message) : new BtoonError(code, message, { cause });
24
+ }
25
+ /**
26
+ * True for the engine's "too much recursion" error. Raising `maxDepth` far above the
27
+ * default can exhaust the JavaScript call stack before the depth limit triggers; that
28
+ * case is reported as `max_depth` instead of leaking a RangeError.
29
+ */
30
+ export function isStackOverflow(e) {
31
+ if (e instanceof BtoonError)
32
+ return false;
33
+ if (typeof e !== 'object' || e === null)
34
+ return false;
35
+ const err = e;
36
+ if (err.name !== 'RangeError' && err.name !== 'InternalError')
37
+ return false;
38
+ return typeof err.message === 'string' && /call stack|recursion/i.test(err.message);
39
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * BTOON for JavaScript and TypeScript — portable entry point (browsers, Deno, Bun-for-web,
3
+ * Cloudflare Workers and other edge runtimes). It imports no `node:` modules.
4
+ *
5
+ * Synchronous `decode` handles zlib compression envelopes (pure-JS inflate) and signed
6
+ * envelopes (pure-JS HMAC-SHA256) everywhere; synchronous compression needs a codec from
7
+ * `options.codecs`, while `encodeAsync` uses `CompressionStream`. Node.js resolves the
8
+ * package to a sibling entry with the same API that uses `node:zlib` and `node:crypto`.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import { type BinaryInput } from './core.js';
13
+ import type { DecodeOptions, EncodeOptions } from './options.js';
14
+ export { SPEC_VERSION, VERSION } from './constants.js';
15
+ export type { BinaryInput } from './core.js';
16
+ export { BtoonError, type BtoonErrorCode } from './errors.js';
17
+ export type { Codec, CodecRegistry, CompressionAlgorithm, DecodeOptions, EncodeOptions } from './options.js';
18
+ export { Ext, Timestamp } from './values.js';
19
+ /**
20
+ * Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
21
+ * in every conforming implementation.
22
+ *
23
+ * Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
24
+ * everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
25
+ * `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
26
+ * typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
27
+ * arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
28
+ * `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
29
+ * the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
30
+ * (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
31
+ * `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
32
+ * rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
33
+ *
34
+ * With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
35
+ * envelope, layered as sign(compress(document)).
36
+ *
37
+ * @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
38
+ * for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
39
+ * with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
40
+ * implementation here (see `encodeAsync` and `options.codecs`).
41
+ */
42
+ export declare function encode(value: unknown, options?: EncodeOptions): Uint8Array<ArrayBuffer>;
43
+ /**
44
+ * Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
45
+ * no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
46
+ * trailing bytes. Compression envelopes are decompressed (with the output capped at the
47
+ * declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
48
+ * anything is parsed.
49
+ *
50
+ * Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
51
+ * `JSON.parse`); it never changes an object's prototype.
52
+ *
53
+ * @throws {BtoonError} with the SPEC §7 error code describing the problem.
54
+ */
55
+ export declare function decode(data: BinaryInput, options?: DecodeOptions): unknown;
56
+ /**
57
+ * Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
58
+ * the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
59
+ * Rejects with a {@link BtoonError}.
60
+ */
61
+ export declare function encodeAsync(value: unknown, options?: EncodeOptions): Promise<Uint8Array<ArrayBuffer>>;
62
+ /**
63
+ * Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
64
+ * supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
65
+ * to the synchronous implementations. The input is copied first, so the caller may reuse
66
+ * its buffer immediately. Rejects with a {@link BtoonError}.
67
+ */
68
+ export declare function decodeAsync(data: BinaryInput, options?: DecodeOptions): Promise<unknown>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * BTOON for JavaScript and TypeScript — portable entry point (browsers, Deno, Bun-for-web,
3
+ * Cloudflare Workers and other edge runtimes). It imports no `node:` modules.
4
+ *
5
+ * Synchronous `decode` handles zlib compression envelopes (pure-JS inflate) and signed
6
+ * envelopes (pure-JS HMAC-SHA256) everywhere; synchronous compression needs a codec from
7
+ * `options.codecs`, while `encodeAsync` uses `CompressionStream`. Node.js resolves the
8
+ * package to a sibling entry with the same API that uses `node:zlib` and `node:crypto`.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import { decodeAsyncWith, decodeWith, encodeAsyncWith, encodeWith } from './core.js';
13
+ import { portablePlatform } from './platform-portable.js';
14
+ export { SPEC_VERSION, VERSION } from './constants.js';
15
+ export { BtoonError } from './errors.js';
16
+ export { Ext, Timestamp } from './values.js';
17
+ /**
18
+ * Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
19
+ * in every conforming implementation.
20
+ *
21
+ * Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
22
+ * everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
23
+ * `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
24
+ * typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
25
+ * arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
26
+ * `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
27
+ * the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
28
+ * (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
29
+ * `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
30
+ * rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
31
+ *
32
+ * With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
33
+ * envelope, layered as sign(compress(document)).
34
+ *
35
+ * @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
36
+ * for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
37
+ * with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
38
+ * implementation here (see `encodeAsync` and `options.codecs`).
39
+ */
40
+ export function encode(value, options) {
41
+ return encodeWith(value, options, portablePlatform);
42
+ }
43
+ /**
44
+ * Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
45
+ * no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
46
+ * trailing bytes. Compression envelopes are decompressed (with the output capped at the
47
+ * declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
48
+ * anything is parsed.
49
+ *
50
+ * Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
51
+ * `JSON.parse`); it never changes an object's prototype.
52
+ *
53
+ * @throws {BtoonError} with the SPEC §7 error code describing the problem.
54
+ */
55
+ export function decode(data, options) {
56
+ return decodeWith(data, options, portablePlatform);
57
+ }
58
+ /**
59
+ * Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
60
+ * the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
61
+ * Rejects with a {@link BtoonError}.
62
+ */
63
+ export function encodeAsync(value, options) {
64
+ return encodeAsyncWith(value, options, portablePlatform);
65
+ }
66
+ /**
67
+ * Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
68
+ * supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
69
+ * to the synchronous implementations. The input is copied first, so the caller may reuse
70
+ * its buffer immediately. Rejects with a {@link BtoonError}.
71
+ */
72
+ export function decodeAsync(data, options) {
73
+ return decodeAsyncWith(data, options, portablePlatform);
74
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Decompresses an RFC 1950 zlib stream that must expand to at most `limit` bytes. Throws
3
+ * `invalid_envelope` for corrupt or truncated input, trailing bytes after the stream, or
4
+ * output beyond `limit`. The output buffer grows geometrically, so a lying size header
5
+ * cannot force a large allocation for a small input.
6
+ */
7
+ export declare function inflateZlib(input: Uint8Array, limit: number): Uint8Array;
8
+ /** Adler-32 checksum of `d[start, end)` (RFC 1950). */
9
+ export declare function adler32(d: Uint8Array, start?: number, end?: number): number;
@@ -0,0 +1,349 @@
1
+ // A small, bounded zlib (RFC 1950) / DEFLATE (RFC 1951) decoder for runtimes without a
2
+ // synchronous native one (browsers, Deno, edge workers). It accepts exactly what zlib's
3
+ // inflate accepts (same rules for incomplete and over-subscribed Huffman codes, distance
4
+ // limits and the Adler-32 trailer), rejects trailing bytes after the stream, and never
5
+ // writes past the declared output size. Work per input byte is bounded: Huffman tables
6
+ // cost at most 512 entries to build, and longer codes are decoded bit by bit.
7
+ import { fail } from './errors.js';
8
+ const FAST_BITS = 9;
9
+ const FAST_SIZE = 1 << FAST_BITS;
10
+ const FAST_MASK = FAST_SIZE - 1;
11
+ const LEN_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
12
+ const LEN_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
13
+ const DIST_BASE = [
14
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
15
+ 8193, 12289, 16385, 24577,
16
+ ];
17
+ const DIST_EXTRA = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
18
+ const CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
19
+ function corrupt(what) {
20
+ return fail('invalid_envelope', `zlib payload ${what}`);
21
+ }
22
+ /** Canonical Huffman code: counts per length, symbols in canonical order, a 9-bit fast table. */
23
+ class Huffman {
24
+ constructor(n) {
25
+ this.count = new Uint16Array(16);
26
+ /** Indexed by the next 9 input bits: (symbol << 4) | length, or 0 for "decode slowly". */
27
+ this.fast = new Uint16Array(FAST_SIZE);
28
+ /** Longest code length in use (0 = no codes). */
29
+ this.max = 0;
30
+ this.symbol = new Uint16Array(n);
31
+ }
32
+ /** Builds from `n` code lengths; returns unused code space (0 = complete, < 0 = over-subscribed). */
33
+ build(lengths, offset, n) {
34
+ const count = this.count;
35
+ count.fill(0);
36
+ for (let i = 0; i < n; i++)
37
+ count[lengths[offset + i]]++;
38
+ count[0] = 0;
39
+ let max = 0;
40
+ let left = 1;
41
+ for (let len = 1; len <= 15; len++) {
42
+ left <<= 1;
43
+ left -= count[len];
44
+ if (left < 0)
45
+ return left;
46
+ if (count[len])
47
+ max = len;
48
+ }
49
+ this.max = max;
50
+ const offs = new Uint16Array(16);
51
+ for (let len = 1; len < 15; len++)
52
+ offs[len + 1] = offs[len] + count[len];
53
+ for (let sym = 0; sym < n; sym++) {
54
+ const len = lengths[offset + sym];
55
+ if (len)
56
+ this.symbol[offs[len]++] = sym;
57
+ }
58
+ const fast = this.fast;
59
+ fast.fill(0);
60
+ let code = 0;
61
+ let idx = 0;
62
+ for (let len = 1; len <= FAST_BITS; len++) {
63
+ for (let k = count[len]; k > 0; k--) {
64
+ let rev = 0;
65
+ for (let b = 0; b < len; b++)
66
+ rev |= ((code >> b) & 1) << (len - 1 - b);
67
+ const entry = (this.symbol[idx] << 4) | len;
68
+ for (let j = rev; j < FAST_SIZE; j += 1 << len)
69
+ fast[j] = entry;
70
+ idx++;
71
+ code++;
72
+ }
73
+ code <<= 1;
74
+ }
75
+ return left;
76
+ }
77
+ }
78
+ let fixedLit = null;
79
+ let fixedDist = null;
80
+ function fixedTables() {
81
+ if (fixedLit === null || fixedDist === null) {
82
+ const l = new Uint8Array(288);
83
+ l.fill(8, 0, 144);
84
+ l.fill(9, 144, 256);
85
+ l.fill(7, 256, 280);
86
+ l.fill(8, 280, 288);
87
+ fixedLit = new Huffman(288);
88
+ fixedLit.build(l, 0, 288);
89
+ const d = new Uint8Array(30).fill(5);
90
+ fixedDist = new Huffman(30);
91
+ fixedDist.build(d, 0, 30); // incomplete (30 of 32 codes), as in zlib
92
+ }
93
+ return [fixedLit, fixedDist];
94
+ }
95
+ class Inflater {
96
+ constructor(input, limit) {
97
+ this.inPos = 0;
98
+ this.bitBuf = 0;
99
+ this.bitCnt = 0;
100
+ this.outPos = 0;
101
+ this.input = input;
102
+ this.limit = limit;
103
+ this.out = new Uint8Array(Math.min(limit, Math.max(64 * 1024, input.length * 4)));
104
+ }
105
+ bits(n) {
106
+ while (this.bitCnt < n) {
107
+ if (this.inPos >= this.input.length)
108
+ throw corrupt('is truncated');
109
+ this.bitBuf |= this.input[this.inPos++] << this.bitCnt;
110
+ this.bitCnt += 8;
111
+ }
112
+ const v = this.bitBuf & ((1 << n) - 1);
113
+ this.bitBuf >>>= n;
114
+ this.bitCnt -= n;
115
+ return v;
116
+ }
117
+ /** Next Huffman symbol, or -1 if the bits match no code. */
118
+ sym(h) {
119
+ while (this.bitCnt < FAST_BITS && this.inPos < this.input.length) {
120
+ this.bitBuf |= this.input[this.inPos++] << this.bitCnt;
121
+ this.bitCnt += 8;
122
+ }
123
+ const e = h.fast[this.bitBuf & FAST_MASK];
124
+ const len = e & 15;
125
+ if (len !== 0) {
126
+ if (len > this.bitCnt)
127
+ throw corrupt('is truncated');
128
+ this.bitBuf >>>= len;
129
+ this.bitCnt -= len;
130
+ return e >> 4;
131
+ }
132
+ // Longer code (or none): canonical decoding, one bit at a time.
133
+ const count = h.count;
134
+ let code = 0;
135
+ let first = 0;
136
+ let index = 0;
137
+ for (let l = 1; l <= 15; l++) {
138
+ code |= this.bits(1);
139
+ const c = count[l];
140
+ if (code - first < c)
141
+ return h.symbol[index + code - first];
142
+ index += c;
143
+ first = (first + c) << 1;
144
+ code <<= 1;
145
+ }
146
+ return -1;
147
+ }
148
+ room(n) {
149
+ const need = this.outPos + n;
150
+ if (need <= this.out.length)
151
+ return;
152
+ if (need > this.limit)
153
+ throw corrupt('decompresses to more than the declared size');
154
+ const next = new Uint8Array(Math.min(this.limit, Math.max(this.out.length * 2, need)));
155
+ next.set(this.out.subarray(0, this.outPos));
156
+ this.out = next;
157
+ }
158
+ /** Byte-aligns the bit reader and hands back whole bytes it had buffered. */
159
+ align() {
160
+ this.inPos -= this.bitCnt >> 3;
161
+ this.bitBuf = 0;
162
+ this.bitCnt = 0;
163
+ }
164
+ stored() {
165
+ this.align();
166
+ const inp = this.input;
167
+ const p = this.inPos;
168
+ if (p + 4 > inp.length)
169
+ throw corrupt('is truncated');
170
+ const len = inp[p] | (inp[p + 1] << 8);
171
+ const nlen = inp[p + 2] | (inp[p + 3] << 8);
172
+ if (len !== (~nlen & 0xffff))
173
+ throw corrupt('is corrupt (invalid stored block lengths)');
174
+ if (p + 4 + len > inp.length)
175
+ throw corrupt('is truncated');
176
+ this.room(len);
177
+ this.out.set(inp.subarray(p + 4, p + 4 + len), this.outPos);
178
+ this.outPos += len;
179
+ this.inPos = p + 4 + len;
180
+ }
181
+ dynamic(lit, dist, cl, lens) {
182
+ const nlen = this.bits(5) + 257;
183
+ const ndist = this.bits(5) + 1;
184
+ const ncode = this.bits(4) + 4;
185
+ if (nlen > 286 || ndist > 30)
186
+ throw corrupt('is corrupt (too many length or distance symbols)');
187
+ const clLens = new Uint8Array(19);
188
+ for (let i = 0; i < ncode; i++)
189
+ clLens[CL_ORDER[i]] = this.bits(3);
190
+ if (cl.build(clLens, 0, 19) !== 0)
191
+ throw corrupt('is corrupt (invalid code lengths set)');
192
+ const total = nlen + ndist;
193
+ let i = 0;
194
+ while (i < total) {
195
+ const s = this.sym(cl);
196
+ if (s < 16) {
197
+ if (s < 0)
198
+ throw corrupt('is corrupt (invalid code lengths set)');
199
+ lens[i++] = s;
200
+ continue;
201
+ }
202
+ let rep;
203
+ let val = 0;
204
+ if (s === 16) {
205
+ if (i === 0)
206
+ throw corrupt('is corrupt (invalid bit length repeat)');
207
+ val = lens[i - 1];
208
+ rep = 3 + this.bits(2);
209
+ }
210
+ else if (s === 17) {
211
+ rep = 3 + this.bits(3);
212
+ }
213
+ else {
214
+ rep = 11 + this.bits(7);
215
+ }
216
+ if (i + rep > total)
217
+ throw corrupt('is corrupt (invalid bit length repeat)');
218
+ while (rep-- > 0)
219
+ lens[i++] = val;
220
+ }
221
+ if (lens[256] === 0)
222
+ throw corrupt('is corrupt (missing end-of-block code)');
223
+ // Incomplete codes are allowed only when they consist of a single 1-bit code (zlib).
224
+ let left = lit.build(lens, 0, nlen);
225
+ if (left < 0 || (left > 0 && lit.max !== 1))
226
+ throw corrupt('is corrupt (invalid literal/lengths set)');
227
+ left = dist.build(lens, nlen, ndist);
228
+ if (left < 0 || (left > 0 && dist.max > 1))
229
+ throw corrupt('is corrupt (invalid distances set)');
230
+ this.codes(lit, dist);
231
+ }
232
+ codes(lit, dist) {
233
+ for (;;) {
234
+ let s = this.sym(lit);
235
+ if (s < 256) {
236
+ if (s < 0)
237
+ throw corrupt('is corrupt (invalid literal/length code)');
238
+ if (this.outPos >= this.out.length)
239
+ this.room(1);
240
+ this.out[this.outPos++] = s;
241
+ continue;
242
+ }
243
+ if (s === 256)
244
+ return;
245
+ s -= 257;
246
+ if (s >= 29)
247
+ throw corrupt('is corrupt (invalid literal/length code)');
248
+ const len = LEN_BASE[s] + this.bits(LEN_EXTRA[s]);
249
+ const ds = this.sym(dist);
250
+ if (ds < 0 || ds >= 30)
251
+ throw corrupt('is corrupt (invalid distance code)');
252
+ const d = DIST_BASE[ds] + this.bits(DIST_EXTRA[ds]);
253
+ if (d > this.outPos)
254
+ throw corrupt('is corrupt (invalid distance too far back)');
255
+ this.room(len);
256
+ const out = this.out;
257
+ let to = this.outPos;
258
+ let from = to - d;
259
+ if (d >= len && len > 16) {
260
+ out.copyWithin(to, from, from + len);
261
+ to += len;
262
+ }
263
+ else {
264
+ for (const end = to + len; to < end;)
265
+ out[to++] = out[from++];
266
+ }
267
+ this.outPos = to;
268
+ }
269
+ }
270
+ run() {
271
+ const inp = this.input;
272
+ if (inp.length < 2)
273
+ throw corrupt('is truncated');
274
+ const cmf = inp[0];
275
+ const flg = inp[1];
276
+ if (((cmf << 8) | flg) % 31 !== 0)
277
+ throw corrupt('is corrupt (incorrect header check)');
278
+ if ((cmf & 0x0f) !== 8)
279
+ throw corrupt('is corrupt (unknown compression method)');
280
+ if (cmf >> 4 > 7)
281
+ throw corrupt('is corrupt (invalid window size)');
282
+ if (flg & 0x20)
283
+ throw corrupt('needs a preset dictionary, which BTOON does not support');
284
+ this.inPos = 2;
285
+ let lit = null;
286
+ let dist = null;
287
+ let cl = null;
288
+ let lens = null;
289
+ let last = 0;
290
+ while (!last) {
291
+ last = this.bits(1);
292
+ const type = this.bits(2);
293
+ if (type === 0) {
294
+ this.stored();
295
+ }
296
+ else if (type === 1) {
297
+ const [fl, fd] = fixedTables();
298
+ this.codes(fl, fd);
299
+ }
300
+ else if (type === 2) {
301
+ if (lit === null) {
302
+ lit = new Huffman(286);
303
+ dist = new Huffman(30);
304
+ cl = new Huffman(19);
305
+ lens = new Uint8Array(286 + 30);
306
+ }
307
+ this.dynamic(lit, dist, cl, lens);
308
+ }
309
+ else {
310
+ throw corrupt('is corrupt (invalid block type)');
311
+ }
312
+ }
313
+ this.align();
314
+ const p = this.inPos;
315
+ if (p + 4 > inp.length)
316
+ throw corrupt('is truncated');
317
+ const expected = ((inp[p] << 24) | (inp[p + 1] << 16) | (inp[p + 2] << 8) | inp[p + 3]) >>> 0;
318
+ if (adler32(this.out, 0, this.outPos) !== expected)
319
+ throw corrupt('is corrupt (incorrect data check)');
320
+ if (p + 4 !== inp.length)
321
+ throw corrupt('has trailing bytes');
322
+ return this.out.subarray(0, this.outPos);
323
+ }
324
+ }
325
+ /**
326
+ * Decompresses an RFC 1950 zlib stream that must expand to at most `limit` bytes. Throws
327
+ * `invalid_envelope` for corrupt or truncated input, trailing bytes after the stream, or
328
+ * output beyond `limit`. The output buffer grows geometrically, so a lying size header
329
+ * cannot force a large allocation for a small input.
330
+ */
331
+ export function inflateZlib(input, limit) {
332
+ return new Inflater(input, limit).run();
333
+ }
334
+ /** Adler-32 checksum of `d[start, end)` (RFC 1950). */
335
+ export function adler32(d, start = 0, end = d.length) {
336
+ let a = 1;
337
+ let b = 0;
338
+ let i = start;
339
+ while (i < end) {
340
+ const stop = Math.min(end, i + 3800); // keeps b below 2^31 between reductions
341
+ for (; i < stop; i++) {
342
+ a += d[i];
343
+ b += a;
344
+ }
345
+ a %= 65521;
346
+ b %= 65521;
347
+ }
348
+ return ((b << 16) | a) >>> 0;
349
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * BTOON for JavaScript and TypeScript — Node.js entry point (also used by Bun). Same API
3
+ * as the portable entry; synchronous compression and signing use `node:zlib` (zlib,
4
+ * Brotli, and Zstandard where Node.js has it) and `node:crypto`.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { type BinaryInput } from './core.js';
9
+ import type { DecodeOptions, EncodeOptions } from './options.js';
10
+ export { SPEC_VERSION, VERSION } from './constants.js';
11
+ export type { BinaryInput } from './core.js';
12
+ export { BtoonError, type BtoonErrorCode } from './errors.js';
13
+ export type { Codec, CodecRegistry, CompressionAlgorithm, DecodeOptions, EncodeOptions } from './options.js';
14
+ export { Ext, Timestamp } from './values.js';
15
+ /**
16
+ * Encodes a value as canonical BTOON (SPEC §4): identical values produce identical bytes
17
+ * in every conforming implementation.
18
+ *
19
+ * Accepted values: `null`/`undefined` (nil), booleans, numbers (safe integers as integers,
20
+ * everything else — including `-0`, `NaN` and integral values beyond ±2^53 — as float64),
21
+ * `bigint`, strings (must not contain lone surrogates), `Uint8Array`/`ArrayBuffer`/other
22
+ * typed arrays and `DataView` (binary), `Float32Array`/`Float64Array` (float vectors),
23
+ * arrays, plain objects and `Map`s with string keys (maps, keys sorted by UTF-8 bytes),
24
+ * `Date` and {@link Timestamp} (timestamps), and {@link Ext}. Any other object is encoded
25
+ * the way `JSON.stringify` sees it: the result of its `toJSON()` method if it has one
26
+ * (called once), otherwise a map of its own enumerable string-keyed properties; `Number`,
27
+ * `String`, `Boolean` and `BigInt` wrapper objects are unwrapped. Functions and symbols are
28
+ * rejected. Arrays of uniform objects use the tabular encoding when it is smaller.
29
+ *
30
+ * With `compress` or `hmacKey`, the document is wrapped in a compression and/or signed
31
+ * envelope, layered as sign(compress(document)).
32
+ *
33
+ * @throws {BtoonError} `invalid_argument` for functions, symbols or invalid options, `max_depth`
34
+ * for values nested deeper than `maxDepth` (including cycles), `invalid_utf8` for strings
35
+ * with lone surrogates, `unsupported_compression` if the algorithm has no synchronous
36
+ * implementation here (see `encodeAsync` and `options.codecs`).
37
+ */
38
+ export declare function encode(value: unknown, options?: EncodeOptions): Uint8Array<ArrayBuffer>;
39
+ /**
40
+ * Decodes one BTOON document, enforcing SPEC §7: bounds-checked reads, a nesting limit,
41
+ * no allocation beyond what the input justifies, strict UTF-8, unique map keys and no
42
+ * trailing bytes. Compression envelopes are decompressed (with the output capped at the
43
+ * declared size and `maxDecompressedSize`); with `hmacKey` the signature is verified before
44
+ * anything is parsed.
45
+ *
46
+ * Maps decode to plain objects. A `"__proto__"` key becomes an own property (as with
47
+ * `JSON.parse`); it never changes an object's prototype.
48
+ *
49
+ * @throws {BtoonError} with the SPEC §7 error code describing the problem.
50
+ */
51
+ export declare function decode(data: BinaryInput, options?: DecodeOptions): unknown;
52
+ /**
53
+ * Like {@link encode}, but compresses with `CompressionStream` (when the runtime supports
54
+ * the algorithm) and signs with WebCrypto, falling back to the synchronous implementations.
55
+ * Rejects with a {@link BtoonError}.
56
+ */
57
+ export declare function encodeAsync(value: unknown, options?: EncodeOptions): Promise<Uint8Array<ArrayBuffer>>;
58
+ /**
59
+ * Like {@link decode}, but decompresses with `DecompressionStream` (when the runtime
60
+ * supports the algorithm) and verifies signatures with `crypto.subtle.verify`, falling back
61
+ * to the synchronous implementations. The input is copied first, so the caller may reuse
62
+ * its buffer immediately. Rejects with a {@link BtoonError}.
63
+ */
64
+ export declare function decodeAsync(data: BinaryInput, options?: DecodeOptions): Promise<unknown>;