@btoon/js 0.0.0-stage → 0.1.1

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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BTOON Contributors
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 CHANGED
@@ -1,3 +1,329 @@
1
- # Temporary Holding Version
1
+ # @btoon/js
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ BTOON ("Binary TOON") for JavaScript and TypeScript. BTOON is
4
+ [MessagePack](https://github.com/msgpack/msgpack/blob/master/spec.md) with a canonical
5
+ encoding, a columnar ("tabular") encoding for arrays of uniform objects, a few extension
6
+ types (timestamp, date, datetime, bigint, float32/float64 vectors), an optional
7
+ compression envelope and an optional HMAC-signed envelope.
8
+
9
+ - **Pure TypeScript, zero runtime dependencies.** Runs in browsers, Node.js ≥ 18, Deno,
10
+ Bun and edge runtimes (Cloudflare Workers, Vercel Edge). ESM and CommonJS builds with
11
+ type definitions.
12
+ - **Conforming.** Implements [BTOON spec 0.1](https://github.com/BTOON-project/btoon-core/blob/main/docs/SPEC.md)
13
+ and passes all 260 conformance vectors of btoon-core (see [Conformance](#conformance)).
14
+ - **Canonical.** The same value encodes to the same bytes as in every other conforming
15
+ implementation (C++, Python, Go, PHP), so encodings can be hashed, cached and signed.
16
+ - **Hardened decoder.** Bounds-checked reads, a nesting limit, no allocation larger than
17
+ the input justifies, strict UTF-8, duplicate-key rejection, capped decompression, and
18
+ signatures verified before anything is parsed.
19
+
20
+ Implements spec 0.1. In Node.js it is also the fastest BTOON package: on
21
+ 10,000 records it encodes in 1.1 ms and decodes in 1.6 ms, against 7.8 ms and 7.1 ms for
22
+ the native [`btoon`](https://github.com/BTOON-project/btoon-nodejs) addon, with
23
+ byte-identical output.
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ npm install @btoon/js
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```js
34
+ import { encode, decode } from '@btoon/js';
35
+
36
+ const bytes = encode({ name: 'Alice', age: 30, active: true });
37
+ // Uint8Array: 83 a6 616374697665 c3 a3 616765 1e a4 6e616d65 a5 416c696365
38
+
39
+ decode(bytes); // { active: true, age: 30, name: 'Alice' }
40
+ ```
41
+
42
+ CommonJS works the same way: `const { encode, decode } = require('@btoon/js')`.
43
+
44
+ In a browser without a bundler, import the ESM build directly (it has no `node:` imports):
45
+
46
+ ```html
47
+ <script type="module">
48
+ import { encode, decode } from './node_modules/@btoon/js/dist/esm/index.js';
49
+ </script>
50
+ ```
51
+
52
+ [`examples/index.html`](examples/index.html) is a working page: run `npm run build`, serve
53
+ the repository (`make serve`, or `python3 -m http.server`), and open `/examples/`.
54
+
55
+ ### Arrays of records become tables
56
+
57
+ Arrays of objects with the same keys are written column by column, with each key once,
58
+ whenever that is smaller (SPEC §5.6.2). They decode back to ordinary arrays of objects.
59
+
60
+ ```js
61
+ const rows = Array.from({ length: 100 }, (_, i) => ({ id: i, name: `user${i}`, active: i % 2 === 0 }));
62
+ encode(rows).length; // 945 bytes
63
+ encode(rows, { autoTabular: false }).length; // 2493 bytes (plain MessagePack)
64
+ JSON.stringify(rows).length; // 4031 bytes
65
+ ```
66
+
67
+ ### Compression and signing
68
+
69
+ ```js
70
+ import { encode, decode, encodeAsync, decodeAsync } from '@btoon/js';
71
+
72
+ const key = crypto.getRandomValues(new Uint8Array(32));
73
+
74
+ const sealed = encode(rows, { compress: true, hmacKey: key }); // Node.js: synchronous
75
+ decode(sealed, { hmacKey: key });
76
+
77
+ const sealed2 = await encodeAsync(rows, { compress: true, hmacKey: key }); // anywhere
78
+ await decodeAsync(sealed2, { hmacKey: key });
79
+ ```
80
+
81
+ Envelopes are layered as sign(compress(document)). With `hmacKey`, `decode` requires a
82
+ signed envelope and verifies it in constant time before parsing; without a key, a signed
83
+ input is rejected rather than silently accepted. Compression is skipped for documents
84
+ under 256 bytes and whenever it would save less than 5% (SPEC §6).
85
+
86
+ ## Environments
87
+
88
+ The package's `exports` map picks an entry point per environment. Both have the same
89
+ API; they differ only in which synchronous codecs are built in.
90
+
91
+ | | Node.js, Bun (`node` condition) | Browsers, Deno, workers, edge (`browser`, `deno`, `worker`, `workerd`, `edge-light`, default) |
92
+ |---|---|---|
93
+ | `decode` of zlib envelopes | `node:zlib` | built-in pure-JS inflate |
94
+ | `decode` of Brotli / Zstandard | `node:zlib` (Zstandard from Node.js 22.15) | `options.codecs` |
95
+ | `encode` with `compress` | zlib, Brotli, Zstandard via `node:zlib` | `options.codecs`, otherwise `unsupported_compression` (use `encodeAsync`) |
96
+ | HMAC in `encode`/`decode` | `node:crypto` | built-in pure-JS SHA-256 |
97
+ | `encodeAsync` / `decodeAsync` compression | `CompressionStream` / `DecompressionStream` where the runtime supports the format, else the synchronous codecs | the same |
98
+ | `encodeAsync` / `decodeAsync` HMAC | WebCrypto (`crypto.subtle`), else `node:crypto` | WebCrypto, else pure JS (for example on `http://` pages) |
99
+ | LZ4, Snappy | `options.codecs` | `options.codecs` |
100
+
101
+ `DecompressionStream('deflate')` is the RFC 1950 zlib format the spec requires. Other
102
+ formats depend on the runtime: at the time of writing, Brotli streams exist in Node.js 22+
103
+ and Bun, and Zstandard streams in Bun.
104
+
105
+ Why a pure-JS inflate and SHA-256 in the portable entry: zlib decoding is mandatory in
106
+ the spec and signatures are a security feature, so synchronous `decode` handles both in
107
+ every environment. Together they add about 8.4 KB minified (3.6 KB gzipped). Inflate
108
+ enforces the declared output size as it goes, and its accept/reject decisions match zlib's
109
+ (tested differentially against `node:zlib`). Synchronous compression is not built in:
110
+ a good deflate encoder is large, and `encodeAsync` or a codec covers it.
111
+
112
+ ### Custom codecs
113
+
114
+ `options.codecs` supplies or overrides compression algorithms. For example, synchronous
115
+ zlib compression in a browser with [fflate](https://github.com/101arrowz/fflate):
116
+
117
+ ```js
118
+ import { zlibSync } from 'fflate';
119
+
120
+ const codecs = { zlib: { compress: (data) => zlibSync(data, { level: 6 }) } };
121
+ const bytes = encode(value, { compress: true, codecs });
122
+ ```
123
+
124
+ A codec is `{ compress?(data), decompress?(data, uncompressedSize) }`. Either function may
125
+ return a `Promise`, but only the async API accepts one. `decompress` receives the size the
126
+ envelope declares (already checked against `maxDecompressedSize`) and must not produce
127
+ more than that. LZ4 block decoders need it as their output size.
128
+
129
+ ## Type mapping
130
+
131
+ Encoding (JavaScript → BTOON):
132
+
133
+ | JavaScript | BTOON |
134
+ |---|---|
135
+ | `null`, `undefined` | nil (object properties and array holes holding `undefined` too) |
136
+ | `boolean` | boolean |
137
+ | `number` that is a safe integer | integer, smallest format |
138
+ | any other `number`, including `-0`, `NaN`, `±Infinity` and integral values beyond ±2^53 | float64 (NaN written as `7ff8000000000000`) |
139
+ | `bigint` | integer if within [-2^63, 2^64-1], else bigint extension |
140
+ | `string` | string (UTF-8). A lone surrogate throws `invalid_utf8` |
141
+ | `Uint8Array` (and `Buffer`), `ArrayBuffer`, `DataView`, other typed arrays | binary (the view's bytes) |
142
+ | `Float32Array`, `Float64Array` | float32 / float64 vector (little-endian, NaN bits preserved) |
143
+ | `Array` | array, or tabular when the size rule says so |
144
+ | plain object, `Map` with string keys | map, keys sorted by UTF-8 bytes |
145
+ | `Date` | timestamp (millisecond precision, UTC) |
146
+ | `Timestamp` | timestamp (nanoseconds, optional UTC offset) |
147
+ | `Ext` | extension, written as is |
148
+ | other objects | like `JSON.stringify`: the result of `toJSON()` if present (called once), else the object's own enumerable string-keyed properties. `Number`/`String`/`Boolean`/`BigInt` wrappers are unwrapped |
149
+ | functions, symbols | `invalid_argument` |
150
+
151
+ Decoding (BTOON → JavaScript):
152
+
153
+ | BTOON | JavaScript |
154
+ |---|---|
155
+ | nil | `null` |
156
+ | integer | `number` if a safe integer, else `bigint`. With `bigint: 'always'`, uint64/int64 markers and bigint extensions always give `bigint` |
157
+ | float | `number` |
158
+ | string | `string` |
159
+ | binary | `Uint8Array` (a copy, never a view of the input) |
160
+ | array, tabular | `Array` |
161
+ | map | plain object |
162
+ | timestamp, date, datetime | `Date` (milliseconds, offset dropped), or `Timestamp` with `timestamps: 'object'`. Instants outside the range of `Date` always give `Timestamp` |
163
+ | float32 / float64 vector | `Float32Array` / `Float64Array` (an aligned copy) |
164
+ | any other extension | `Ext` |
165
+
166
+ Notes:
167
+
168
+ - **Numbers.** JavaScript can't tell `2.0` from `2`, so whole numbers encode as integers.
169
+ `-0` is the exception: it encodes as float64 `-0.0` so its sign survives.
170
+ - **`__proto__`.** A map key `"__proto__"` becomes an own property of the decoded object,
171
+ as with `JSON.parse`. It never changes the object's prototype.
172
+ - **Key order.** Keys are sorted by their UTF-8 bytes, which is not JavaScript's default
173
+ `sort()` order for characters outside the Basic Multilingual Plane.
174
+ - **`date` and `datetime`.** These extension types have no JavaScript type of their own and
175
+ decode to `Date` (or `Timestamp`). To write one, use `new Ext(-2, bytes)` (int64
176
+ milliseconds) or `new Ext(-3, bytes)` (int64 nanoseconds). An `Ext` with an id BTOON
177
+ defines must carry a valid payload for that type, or `encode` throws `invalid_argument`.
178
+ - **Cycles** throw `max_depth`, like any value nested deeper than `maxDepth`.
179
+ - `Ext` and `Timestamp` are recognised through `Symbol.for('btoon.Ext')` and
180
+ `Symbol.for('btoon.Timestamp')` brands, so instances from the ESM and CommonJS builds
181
+ (or two copies of the package) are interchangeable.
182
+
183
+ ## API
184
+
185
+ ```ts
186
+ const VERSION: '0.1.1';
187
+ const SPEC_VERSION: '0.1';
188
+
189
+ function encode(value: unknown, options?: EncodeOptions): Uint8Array;
190
+ function decode(data: Uint8Array | ArrayBuffer | ArrayBufferView, options?: DecodeOptions): unknown;
191
+ function encodeAsync(value: unknown, options?: EncodeOptions): Promise<Uint8Array>;
192
+ function decodeAsync(data: Uint8Array | ArrayBuffer | ArrayBufferView, options?: DecodeOptions): Promise<unknown>;
193
+
194
+ class BtoonError extends Error { readonly code: BtoonErrorCode }
195
+ class Ext { constructor(readonly type: number, readonly data: Uint8Array) }
196
+ class Timestamp {
197
+ constructor(seconds: bigint | number, nanoseconds: number, offsetMinutes?: number);
198
+ readonly seconds: bigint; readonly nanoseconds: number; readonly offsetMinutes?: number;
199
+ toDate(): Date;
200
+ static fromDate(date: Date): Timestamp;
201
+ }
202
+ ```
203
+
204
+ `encode` returns `Uint8Array<ArrayBuffer>`; the type definitions need TypeScript 5.7 or
205
+ later (or `skipLibCheck`). `decodeAsync` copies its input first, so the caller may reuse
206
+ the buffer right away. The async functions report every failure as a rejected promise.
207
+
208
+ ### Encode options
209
+
210
+ | Option | Default | Meaning |
211
+ |---|---|---|
212
+ | `compress` | `false` | Wrap in a compression envelope. |
213
+ | `compression` | `'zlib'` | `'zlib'`, `'lz4'`, `'zstd'`, `'brotli'` or `'snappy'`. Only used when `compress` is true. |
214
+ | `autoTabular` | `true` | Use the tabular encoding where the size rule says so. |
215
+ | `maxDepth` | `128` | Nesting limit. Deeper or cyclic values throw `max_depth`. |
216
+ | `hmacKey` | none | `Uint8Array` or string (its UTF-8 bytes). Signs the output. Must not be empty. |
217
+ | `codecs` | none | Caller-supplied compression codecs. |
218
+
219
+ ### Decode options
220
+
221
+ | Option | Default | Meaning |
222
+ |---|---|---|
223
+ | `strict` | `true` | SPEC §7 strict mode. `false` allows trailing bytes and duplicate map keys (last wins) and replaces invalid UTF-8 with U+FFFD. Bounds, depth, allocation and other structural checks are never relaxed. |
224
+ | `maxDepth` | `128` | Nesting limit. A tabular array counts two levels. |
225
+ | `maxDecompressedSize` | 256 MiB | Largest accepted `uncompressed_size`, checked before decompressing. |
226
+ | `autoDecompress` | `true` | Accept compression envelopes; `false` rejects them with `invalid_envelope`. |
227
+ | `hmacKey` | none | Require a signed envelope and verify it before parsing. Must not be empty. |
228
+ | `timestamps` | `'date'` | `'date'` or `'object'` (always `Timestamp`, full precision). |
229
+ | `bigint` | `'auto'` | `'auto'` or `'always'`. |
230
+ | `codecs` | none | Caller-supplied compression codecs. |
231
+
232
+ Invalid options throw `invalid_argument`. Unknown option names are ignored.
233
+
234
+ ## Errors
235
+
236
+ Every failure is a `BtoonError` whose `code` is stable across BTOON implementations:
237
+
238
+ | Code | Meaning |
239
+ |---|---|
240
+ | `truncated` | Input ends before a value is complete, or a length runs past the input. |
241
+ | `invalid_marker` | The reserved byte `0xc1`. |
242
+ | `trailing_data` | Bytes after the top-level value (strict mode). |
243
+ | `invalid_utf8` | A string, key or column name isn't valid UTF-8, or a string to encode has a lone surrogate. |
244
+ | `non_string_key` | A map key that isn't a string. |
245
+ | `duplicate_key` | The same map key twice (strict mode). |
246
+ | `max_depth` | Nesting deeper than `maxDepth`, or a cyclic value. |
247
+ | `invalid_extension` | A malformed timestamp, date, datetime, bigint or vector payload. |
248
+ | `invalid_tabular` | A malformed tabular payload. |
249
+ | `invalid_envelope` | A malformed envelope, or a compressed payload that is corrupt, has trailing bytes, or decompresses to a size other than the declared one. |
250
+ | `size_limit` | A declared size above `maxDecompressedSize`, or a value too large for the engine. |
251
+ | `unsupported_compression` | An unknown algorithm id, or one not available in this environment. |
252
+ | `authentication_failed` | A missing or invalid signature, or a signed input decoded without a key. |
253
+ | `invalid_argument` | A value that can't be encoded, or an invalid option. |
254
+
255
+ ```js
256
+ try {
257
+ decode(bytes);
258
+ } catch (e) {
259
+ if (e instanceof BtoonError && e.code === 'truncated') { /* … */ }
260
+ }
261
+ ```
262
+
263
+ Exceptions thrown by your own `toJSON()` methods or getters propagate unchanged.
264
+
265
+ ## Security
266
+
267
+ Decoding is designed for hostile input (SPEC §7, §9): every read is bounds-checked; arrays,
268
+ maps and tables are checked against the remaining input before anything is allocated; the
269
+ nesting limit applies to input and output; strings are validated with a fatal
270
+ `TextDecoder`; compressed payloads are checked against `maxDecompressedSize` before
271
+ decompressing and stopped at their declared size while decompressing; Zstandard frames
272
+ whose window exceeds max(8 MiB, min(declared size, 128 MiB)) are rejected; HMACs are
273
+ compared in constant time and checked before the payload is parsed. Raising `maxDepth`
274
+ far above the default is safe: exhausting the JavaScript stack is reported as
275
+ `max_depth`.
276
+
277
+ ## Conformance
278
+
279
+ The test suite runs btoon-core's conformance vectors (vendored in `test/vectors/`) against
280
+ the ESM and CommonJS builds of both entry points, synchronously and asynchronously: every
281
+ non-error vector must decode to its value, every canonical vector must re-encode
282
+ byte-for-byte, and every error vector must fail with its error code. All 260 pass, with
283
+ these exceptions:
284
+
285
+ - `float/0` is not re-encoded: the float `0.0` is indistinguishable from the integer `0`
286
+ in JavaScript (it is still decoded and checked).
287
+ - Vectors that need an optional codec are skipped where it is missing: LZ4 everywhere;
288
+ Brotli and Zstandard in the portable entry without `codecs` (and Zstandard on
289
+ Node.js < 22.15).
290
+
291
+ ## Performance
292
+
293
+ Measured with `npm run bench` (Node.js 22.22.2, Apple M4 Max, best of 7 rounds; Node
294
+ entry). Throughput is relative to the BTOON size. JSON figures are for comparison only.
295
+
296
+ | Case | BTOON size | encode | decode | JSON size | `JSON.stringify` | `JSON.parse` |
297
+ |---|---|---|---|---|---|---|
298
+ | 10k records, tabular | 571,851 B | 1.54 ms | 2.92 ms | 1,242,300 B | 2.01 ms | 2.80 ms |
299
+ | 10k records, `autoTabular: false` | 941,744 B | 3.76 ms | 3.97 ms | 1,242,300 B | 2.01 ms | 2.81 ms |
300
+ | 2k nested objects | 177,644 B | 0.74 ms | 0.66 ms | 298,714 B | 0.63 ms | 0.96 ms |
301
+
302
+ The portable entry measured up to 6% slower than these numbers on the same machine.
303
+
304
+ Bundle size of the portable ESM entry with everything imported, bundled with esbuild
305
+ 0.28.2 (`--minify`): 44.1 KB, 14.7 KB gzipped. An application that imports only `decode`
306
+ bundles 25.9 KB (9.4 KB gzipped).
307
+
308
+ ## Development
309
+
310
+ ```sh
311
+ npm install
312
+ npm run build # dist/esm and dist/cjs, each with .d.ts files (tsc)
313
+ npm run typecheck # strict type check of src/
314
+ npm test # node:test suite against dist/ (vectors, unit, fuzz, packaging)
315
+ npm run bench
316
+ npm run vectors:sync # re-copy the vectors from ../btoon-core (or a path argument)
317
+ ```
318
+
319
+ `npm test -- fuzz vectors` runs only the matching test files. `FUZZ_ITERATIONS`,
320
+ `FUZZ_SEED`, `INFLATE_MUTATIONS` and `CANONICAL_ITERATIONS` scale the randomized tests.
321
+ The `Makefile` wraps the same commands. CI runs type checking, the build and the tests on
322
+ Node.js 20, 22, 24 and 26.
323
+
324
+ Never edit `test/vectors/btoon-vectors.json` by hand: it is generated by btoon-core's
325
+ `tests/vectors/generate.py`.
326
+
327
+ ## License
328
+
329
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,34 @@
1
+ /** Version of this library. */
2
+ export declare const VERSION: "0.1.1";
3
+ /** Version of the BTOON specification this library implements. */
4
+ export declare const SPEC_VERSION: "0.1";
5
+ export declare const EXT_TIMESTAMP = -1;
6
+ export declare const EXT_DATE = -2;
7
+ export declare const EXT_DATETIME = -3;
8
+ export declare const EXT_BIGINT = -4;
9
+ export declare const EXT_F32_VECTOR = -5;
10
+ export declare const EXT_F64_VECTOR = -6;
11
+ export declare const EXT_TABULAR = -10;
12
+ export declare const TABULAR_VERSION = 1;
13
+ export declare const MAX_OFFSET_MINUTES = 1080;
14
+ export declare const NANOS_PER_SECOND = 1000000000;
15
+ export declare const DEFAULT_MAX_DEPTH = 128;
16
+ export declare const DEFAULT_MAX_DECOMPRESSED_SIZE: number;
17
+ /** Encoders SHOULD skip compression below this size (SPEC §6). */
18
+ export declare const MIN_COMPRESSION_SIZE = 256;
19
+ export declare const COMPRESSION_HEADER_SIZE = 16;
20
+ export declare const SIGNED_HEADER_SIZE = 40;
21
+ /** Compression algorithm ids of the envelope header (SPEC §6). */
22
+ export declare const ALGORITHM_IDS: {
23
+ readonly zlib: 0;
24
+ readonly lz4: 1;
25
+ readonly zstd: 2;
26
+ readonly brotli: 3;
27
+ readonly snappy: 4;
28
+ };
29
+ export declare const ALGORITHM_NAMES: readonly ["zlib", "lz4", "zstd", "brotli", "snappy"];
30
+ /** Brands that identify {@link Ext} and {@link Timestamp} instances across module copies. */
31
+ export declare const EXT_BRAND: unique symbol;
32
+ export declare const TIMESTAMP_BRAND: unique symbol;
33
+ /** Date's representable range is ±8.64e15 ms around the epoch. */
34
+ export declare const MAX_DATE_MS = 8640000000000000;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_DATE_MS = exports.TIMESTAMP_BRAND = exports.EXT_BRAND = exports.ALGORITHM_NAMES = exports.ALGORITHM_IDS = exports.SIGNED_HEADER_SIZE = exports.COMPRESSION_HEADER_SIZE = exports.MIN_COMPRESSION_SIZE = exports.DEFAULT_MAX_DECOMPRESSED_SIZE = exports.DEFAULT_MAX_DEPTH = exports.NANOS_PER_SECOND = exports.MAX_OFFSET_MINUTES = exports.TABULAR_VERSION = exports.EXT_TABULAR = exports.EXT_F64_VECTOR = exports.EXT_F32_VECTOR = exports.EXT_BIGINT = exports.EXT_DATETIME = exports.EXT_DATE = exports.EXT_TIMESTAMP = exports.SPEC_VERSION = exports.VERSION = void 0;
4
+ /** Version of this library. */
5
+ exports.VERSION = '0.1.1';
6
+ /** Version of the BTOON specification this library implements. */
7
+ exports.SPEC_VERSION = '0.1';
8
+ // Extension type ids (SPEC §5).
9
+ exports.EXT_TIMESTAMP = -1;
10
+ exports.EXT_DATE = -2;
11
+ exports.EXT_DATETIME = -3;
12
+ exports.EXT_BIGINT = -4;
13
+ exports.EXT_F32_VECTOR = -5;
14
+ exports.EXT_F64_VECTOR = -6;
15
+ exports.EXT_TABULAR = -10;
16
+ exports.TABULAR_VERSION = 1;
17
+ exports.MAX_OFFSET_MINUTES = 1080;
18
+ exports.NANOS_PER_SECOND = 1000000000;
19
+ exports.DEFAULT_MAX_DEPTH = 128;
20
+ exports.DEFAULT_MAX_DECOMPRESSED_SIZE = 256 * 1024 * 1024;
21
+ /** Encoders SHOULD skip compression below this size (SPEC §6). */
22
+ exports.MIN_COMPRESSION_SIZE = 256;
23
+ exports.COMPRESSION_HEADER_SIZE = 16;
24
+ exports.SIGNED_HEADER_SIZE = 40;
25
+ /** Compression algorithm ids of the envelope header (SPEC §6). */
26
+ exports.ALGORITHM_IDS = { zlib: 0, lz4: 1, zstd: 2, brotli: 3, snappy: 4 };
27
+ exports.ALGORITHM_NAMES = ['zlib', 'lz4', 'zstd', 'brotli', 'snappy'];
28
+ /** Brands that identify {@link Ext} and {@link Timestamp} instances across module copies. */
29
+ exports.EXT_BRAND = Symbol.for('btoon.Ext');
30
+ exports.TIMESTAMP_BRAND = Symbol.for('btoon.Timestamp');
31
+ /** Date's representable range is ±8.64e15 ms around the epoch. */
32
+ exports.MAX_DATE_MS = 8640000000000000;
@@ -0,0 +1,15 @@
1
+ import { type CompressionAlgorithm, type DecodeOptions, type EncodeOptions } from './options.js';
2
+ /** Synchronous building blocks of one environment. */
3
+ export interface Platform {
4
+ /** Decompressors; `size` is the declared uncompressed size (already checked against the limit). */
5
+ readonly decompress: Readonly<Partial<Record<CompressionAlgorithm, (data: Uint8Array, size: number) => Uint8Array>>>;
6
+ readonly compress: Readonly<Partial<Record<CompressionAlgorithm, (data: Uint8Array) => Uint8Array>>>;
7
+ hmac(key: Uint8Array, parts: readonly Uint8Array[]): Uint8Array;
8
+ equal(a: Uint8Array, b: Uint8Array): boolean;
9
+ }
10
+ /** Input accepted by `decode`. */
11
+ export type BinaryInput = Uint8Array | ArrayBuffer | ArrayBufferView;
12
+ export declare function encodeWith(value: unknown, options: EncodeOptions | undefined, platform: Platform): Uint8Array<ArrayBuffer>;
13
+ export declare function decodeWith(data: BinaryInput, options: DecodeOptions | undefined, platform: Platform): unknown;
14
+ export declare function encodeAsyncWith(value: unknown, options: EncodeOptions | undefined, platform: Platform): Promise<Uint8Array<ArrayBuffer>>;
15
+ export declare function decodeAsyncWith(data: BinaryInput, options: DecodeOptions | undefined, platform: Platform): Promise<unknown>;