@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.
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/dist/cjs/constants.d.ts +34 -0
- package/dist/cjs/constants.js +32 -0
- package/dist/cjs/core.d.ts +15 -0
- package/dist/cjs/core.js +275 -0
- package/dist/cjs/decode.d.ts +10 -0
- package/dist/cjs/decode.js +554 -0
- package/dist/cjs/encode.d.ts +2 -0
- package/dist/cjs/encode.js +946 -0
- package/dist/cjs/errors.d.ts +53 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +86 -0
- package/dist/cjs/inflate.d.ts +9 -0
- package/dist/cjs/inflate.js +353 -0
- package/dist/cjs/node.d.ts +64 -0
- package/dist/cjs/node.js +82 -0
- package/dist/cjs/options.d.ts +95 -0
- package/dist/cjs/options.js +128 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform-node.d.ts +3 -0
- package/dist/cjs/platform-node.js +132 -0
- package/dist/cjs/platform-portable.d.ts +7 -0
- package/dist/cjs/platform-portable.js +16 -0
- package/dist/cjs/sha256.d.ts +5 -0
- package/dist/cjs/sha256.js +141 -0
- package/dist/cjs/utf8.d.ts +19 -0
- package/dist/cjs/utf8.js +107 -0
- package/dist/cjs/values.d.ts +58 -0
- package/dist/cjs/values.js +129 -0
- package/dist/cjs/web.d.ts +25 -0
- package/dist/cjs/web.js +151 -0
- package/dist/esm/constants.d.ts +34 -0
- package/dist/esm/constants.js +29 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +269 -0
- package/dist/esm/decode.d.ts +10 -0
- package/dist/esm/decode.js +549 -0
- package/dist/esm/encode.d.ts +2 -0
- package/dist/esm/encode.js +943 -0
- package/dist/esm/errors.d.ts +53 -0
- package/dist/esm/errors.js +39 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +74 -0
- package/dist/esm/inflate.d.ts +9 -0
- package/dist/esm/inflate.js +349 -0
- package/dist/esm/node.d.ts +64 -0
- package/dist/esm/node.js +70 -0
- package/dist/esm/options.d.ts +95 -0
- package/dist/esm/options.js +124 -0
- package/dist/esm/platform-node.d.ts +3 -0
- package/dist/esm/platform-node.js +96 -0
- package/dist/esm/platform-portable.d.ts +7 -0
- package/dist/esm/platform-portable.js +13 -0
- package/dist/esm/sha256.d.ts +5 -0
- package/dist/esm/sha256.js +136 -0
- package/dist/esm/utf8.d.ts +19 -0
- package/dist/esm/utf8.js +99 -0
- package/dist/esm/values.d.ts +58 -0
- package/dist/esm/values.js +120 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.js +143 -0
- package/package.json +85 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Version of this library. */
|
|
2
|
+
export const VERSION = '0.1.1';
|
|
3
|
+
/** Version of the BTOON specification this library implements. */
|
|
4
|
+
export const SPEC_VERSION = '0.1';
|
|
5
|
+
// Extension type ids (SPEC §5).
|
|
6
|
+
export const EXT_TIMESTAMP = -1;
|
|
7
|
+
export const EXT_DATE = -2;
|
|
8
|
+
export const EXT_DATETIME = -3;
|
|
9
|
+
export const EXT_BIGINT = -4;
|
|
10
|
+
export const EXT_F32_VECTOR = -5;
|
|
11
|
+
export const EXT_F64_VECTOR = -6;
|
|
12
|
+
export const EXT_TABULAR = -10;
|
|
13
|
+
export const TABULAR_VERSION = 1;
|
|
14
|
+
export const MAX_OFFSET_MINUTES = 1080;
|
|
15
|
+
export const NANOS_PER_SECOND = 1000000000;
|
|
16
|
+
export const DEFAULT_MAX_DEPTH = 128;
|
|
17
|
+
export const DEFAULT_MAX_DECOMPRESSED_SIZE = 256 * 1024 * 1024;
|
|
18
|
+
/** Encoders SHOULD skip compression below this size (SPEC §6). */
|
|
19
|
+
export const MIN_COMPRESSION_SIZE = 256;
|
|
20
|
+
export const COMPRESSION_HEADER_SIZE = 16;
|
|
21
|
+
export const SIGNED_HEADER_SIZE = 40;
|
|
22
|
+
/** Compression algorithm ids of the envelope header (SPEC §6). */
|
|
23
|
+
export const ALGORITHM_IDS = { zlib: 0, lz4: 1, zstd: 2, brotli: 3, snappy: 4 };
|
|
24
|
+
export const ALGORITHM_NAMES = ['zlib', 'lz4', 'zstd', 'brotli', 'snappy'];
|
|
25
|
+
/** Brands that identify {@link Ext} and {@link Timestamp} instances across module copies. */
|
|
26
|
+
export const EXT_BRAND = Symbol.for('btoon.Ext');
|
|
27
|
+
export const TIMESTAMP_BRAND = Symbol.for('btoon.Timestamp');
|
|
28
|
+
/** Date's representable range is ±8.64e15 ms around the epoch. */
|
|
29
|
+
export const 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>;
|
package/dist/esm/core.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// encode/decode with the compression (SPEC §6) and signed (SPEC §8) envelopes, layered as
|
|
2
|
+
// sign(compress(encode(value))). The entry points bind these to a Platform: the set of
|
|
3
|
+
// synchronous codecs available in that environment.
|
|
4
|
+
import { ALGORITHM_IDS, ALGORITHM_NAMES, COMPRESSION_HEADER_SIZE, MIN_COMPRESSION_SIZE, SIGNED_HEADER_SIZE, } from './constants.js';
|
|
5
|
+
import { decodeDocument, plainBytes } from './decode.js';
|
|
6
|
+
import { encodeDocument } from './encode.js';
|
|
7
|
+
import { BtoonError, fail } from './errors.js';
|
|
8
|
+
import { decodeConfig, encodeConfig, } from './options.js';
|
|
9
|
+
import { isUint8Array } from './values.js';
|
|
10
|
+
import { subtle, webCompress, webDecompress, webFormat, webHmacSign, webHmacVerify } from './web.js';
|
|
11
|
+
const COMPRESSED_MAGIC = [0x42, 0x54, 0x4f, 0x4e]; // "BTON"
|
|
12
|
+
const SIGNED_MAGIC = [0x42, 0x54, 0x53, 0x47]; // "BTSG"
|
|
13
|
+
const SIGNED_HEADER = new Uint8Array([0x42, 0x54, 0x53, 0x47, 1, 1, 0, 0]);
|
|
14
|
+
const objectToString = Object.prototype.toString;
|
|
15
|
+
function hasMagic(b, magic) {
|
|
16
|
+
return b.length >= 4 && b[0] === magic[0] && b[1] === magic[1] && b[2] === magic[2] && b[3] === magic[3];
|
|
17
|
+
}
|
|
18
|
+
function inputBytes(data) {
|
|
19
|
+
if (ArrayBuffer.isView(data))
|
|
20
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
21
|
+
const tag = objectToString.call(data);
|
|
22
|
+
if (tag === '[object ArrayBuffer]' || tag === '[object SharedArrayBuffer]')
|
|
23
|
+
return new Uint8Array(data);
|
|
24
|
+
throw fail('invalid_argument', 'decode() expects a Uint8Array, ArrayBuffer or other ArrayBufferView');
|
|
25
|
+
}
|
|
26
|
+
function isShared(b) {
|
|
27
|
+
return typeof SharedArrayBuffer !== 'undefined' && b.buffer instanceof SharedArrayBuffer;
|
|
28
|
+
}
|
|
29
|
+
function isThenable(v) {
|
|
30
|
+
return (typeof v === 'object' || typeof v === 'function') && v !== null && typeof v.then === 'function';
|
|
31
|
+
}
|
|
32
|
+
function messageOf(e) {
|
|
33
|
+
return e instanceof Error ? e.message || e.name : String(e);
|
|
34
|
+
}
|
|
35
|
+
// --- compression envelope -----------------------------------------------------------------
|
|
36
|
+
function envelopeIfSmaller(raw, payload, alg) {
|
|
37
|
+
// SPEC §6: only when it saves at least 5%, i.e. 20 × envelope_size ≤ 19 × input_size.
|
|
38
|
+
if (20 * (COMPRESSION_HEADER_SIZE + payload.length) > 19 * raw.length)
|
|
39
|
+
return raw;
|
|
40
|
+
const out = new Uint8Array(COMPRESSION_HEADER_SIZE + payload.length);
|
|
41
|
+
out.set(COMPRESSED_MAGIC, 0);
|
|
42
|
+
out[4] = 1;
|
|
43
|
+
out[5] = ALGORITHM_IDS[alg];
|
|
44
|
+
const view = new DataView(out.buffer);
|
|
45
|
+
view.setUint32(8, payload.length);
|
|
46
|
+
view.setUint32(12, raw.length);
|
|
47
|
+
out.set(payload, COMPRESSION_HEADER_SIZE);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Checks the header of a compression envelope (before any decompression), or returns null. */
|
|
51
|
+
function compressionEnvelope(b, cfg) {
|
|
52
|
+
if (b.length < COMPRESSION_HEADER_SIZE || !hasMagic(b, COMPRESSED_MAGIC))
|
|
53
|
+
return null;
|
|
54
|
+
if (!cfg.autoDecompress)
|
|
55
|
+
throw fail('invalid_envelope', 'input is compressed but autoDecompress is disabled');
|
|
56
|
+
const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
57
|
+
if (b[4] !== 1)
|
|
58
|
+
throw fail('invalid_envelope', `unsupported compression envelope version ${b[4]}`);
|
|
59
|
+
if (view.getUint16(6) !== 0)
|
|
60
|
+
throw fail('invalid_envelope', 'compression envelope reserved bytes must be zero');
|
|
61
|
+
if (view.getUint32(8) !== b.length - COMPRESSION_HEADER_SIZE) {
|
|
62
|
+
throw fail('invalid_envelope', 'compressed size does not match the envelope length');
|
|
63
|
+
}
|
|
64
|
+
const size = view.getUint32(12);
|
|
65
|
+
if (size > cfg.maxDecompressedSize) {
|
|
66
|
+
throw fail('size_limit', `declared uncompressed size ${size} exceeds maxDecompressedSize (${cfg.maxDecompressedSize})`);
|
|
67
|
+
}
|
|
68
|
+
const alg = ALGORITHM_NAMES[b[5]];
|
|
69
|
+
if (alg === undefined)
|
|
70
|
+
throw fail('unsupported_compression', `unknown compression algorithm id ${b[5]}`);
|
|
71
|
+
return { alg, payload: b.subarray(COMPRESSION_HEADER_SIZE), size };
|
|
72
|
+
}
|
|
73
|
+
function checkSize(out, size, alg) {
|
|
74
|
+
if (out.length !== size)
|
|
75
|
+
throw fail('invalid_envelope', `${alg} payload decompressed size does not match the header`);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function unavailable(alg, op, asyncWorks) {
|
|
79
|
+
const fn = op === 'compression' ? 'encodeAsync()' : 'decodeAsync()';
|
|
80
|
+
return fail('unsupported_compression', asyncWorks
|
|
81
|
+
? `${alg} ${op} is not available synchronously in this environment; use ${fn} or pass options.codecs.${alg}`
|
|
82
|
+
: `${alg} ${op} is not available in this environment; pass options.codecs.${alg}`);
|
|
83
|
+
}
|
|
84
|
+
function codecResult(r, alg, fn) {
|
|
85
|
+
if (!isUint8Array(r))
|
|
86
|
+
throw fail('invalid_argument', `codecs.${alg}.${fn} must return a Uint8Array`);
|
|
87
|
+
return plainBytes(r);
|
|
88
|
+
}
|
|
89
|
+
function codecError(e, alg, fn) {
|
|
90
|
+
if (e instanceof BtoonError)
|
|
91
|
+
return e;
|
|
92
|
+
return fail(fn === 'decompress' ? 'invalid_envelope' : 'invalid_argument', `codecs.${alg}.${fn} failed: ${messageOf(e)}`, e);
|
|
93
|
+
}
|
|
94
|
+
function callCodecSync(codecs, alg, fn, args) {
|
|
95
|
+
const codec = codecs?.[alg];
|
|
96
|
+
const f = codec?.[fn];
|
|
97
|
+
if (f === undefined)
|
|
98
|
+
return undefined;
|
|
99
|
+
let r;
|
|
100
|
+
try {
|
|
101
|
+
r = f.apply(codec, args);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw codecError(e, alg, fn);
|
|
105
|
+
}
|
|
106
|
+
if (isThenable(r)) {
|
|
107
|
+
Promise.resolve(r).catch(() => undefined);
|
|
108
|
+
throw fail('invalid_argument', `codecs.${alg}.${fn} returned a Promise; use the async API (encodeAsync/decodeAsync)`);
|
|
109
|
+
}
|
|
110
|
+
return codecResult(r, alg, fn);
|
|
111
|
+
}
|
|
112
|
+
async function callCodecAsync(codecs, alg, fn, args) {
|
|
113
|
+
const codec = codecs?.[alg];
|
|
114
|
+
const f = codec?.[fn];
|
|
115
|
+
if (f === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
let r;
|
|
118
|
+
try {
|
|
119
|
+
r = await f.apply(codec, args);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
throw codecError(e, alg, fn);
|
|
123
|
+
}
|
|
124
|
+
return codecResult(r, alg, fn);
|
|
125
|
+
}
|
|
126
|
+
function hasCodec(codecs, alg, fn) {
|
|
127
|
+
return typeof codecs?.[alg]?.[fn] === 'function';
|
|
128
|
+
}
|
|
129
|
+
function signedEnvelope(b) {
|
|
130
|
+
if (!hasMagic(b, SIGNED_MAGIC))
|
|
131
|
+
throw fail('authentication_failed', 'input is not a signed envelope');
|
|
132
|
+
if (b.length < SIGNED_HEADER_SIZE)
|
|
133
|
+
throw fail('invalid_envelope', 'signed envelope is truncated');
|
|
134
|
+
if (b[4] !== 1 || b[5] !== 1 || b[6] !== 0 || b[7] !== 0) {
|
|
135
|
+
throw fail('invalid_envelope', 'unsupported signed envelope version or algorithm');
|
|
136
|
+
}
|
|
137
|
+
return { header: b.subarray(0, 8), mac: b.subarray(8, SIGNED_HEADER_SIZE), payload: b.subarray(SIGNED_HEADER_SIZE) };
|
|
138
|
+
}
|
|
139
|
+
function rejectUnexpectedSignature(b) {
|
|
140
|
+
if (b.length >= SIGNED_HEADER_SIZE && hasMagic(b, SIGNED_MAGIC)) {
|
|
141
|
+
throw fail('authentication_failed', 'input is signed but no hmacKey was provided');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function assembleSigned(mac, payload) {
|
|
145
|
+
const out = new Uint8Array(SIGNED_HEADER_SIZE + payload.length);
|
|
146
|
+
out.set(SIGNED_HEADER, 0);
|
|
147
|
+
out.set(mac, 8);
|
|
148
|
+
out.set(payload, SIGNED_HEADER_SIZE);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function concat2(a, b) {
|
|
152
|
+
const out = new Uint8Array(a.length + b.length);
|
|
153
|
+
out.set(a, 0);
|
|
154
|
+
out.set(b, a.length);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
// --- synchronous API ------------------------------------------------------------------------
|
|
158
|
+
export function encodeWith(value, options, platform) {
|
|
159
|
+
const cfg = encodeConfig(options);
|
|
160
|
+
let out = encodeDocument(value, cfg.maxDepth, cfg.autoTabular);
|
|
161
|
+
if (cfg.compress) {
|
|
162
|
+
const alg = cfg.algorithm;
|
|
163
|
+
const builtin = platform.compress[alg];
|
|
164
|
+
// Fail fast, even for documents too small to compress, so a missing codec shows up in testing.
|
|
165
|
+
if (!hasCodec(cfg.codecs, alg, 'compress') && builtin === undefined) {
|
|
166
|
+
throw unavailable(alg, 'compression', webFormat(alg, 'CompressionStream') !== undefined);
|
|
167
|
+
}
|
|
168
|
+
if (out.length >= MIN_COMPRESSION_SIZE) {
|
|
169
|
+
if (out.length > 0xffffffff)
|
|
170
|
+
throw fail('size_limit', 'document too large to compress');
|
|
171
|
+
const payload = callCodecSync(cfg.codecs, alg, 'compress', [out]) ?? builtin(out);
|
|
172
|
+
out = envelopeIfSmaller(out, payload, alg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (cfg.hmacKey !== undefined)
|
|
176
|
+
out = assembleSigned(platform.hmac(cfg.hmacKey, [SIGNED_HEADER, out]), out);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
export function decodeWith(data, options, platform) {
|
|
180
|
+
const cfg = decodeConfig(options);
|
|
181
|
+
let bytes = inputBytes(data);
|
|
182
|
+
if (cfg.hmacKey !== undefined) {
|
|
183
|
+
if (isShared(bytes))
|
|
184
|
+
bytes = bytes.slice(); // verify and parse the same bytes
|
|
185
|
+
const env = signedEnvelope(bytes);
|
|
186
|
+
const expected = platform.hmac(cfg.hmacKey, [env.header, env.payload]);
|
|
187
|
+
if (!platform.equal(expected, env.mac))
|
|
188
|
+
throw fail('authentication_failed', 'HMAC verification failed');
|
|
189
|
+
bytes = env.payload;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
rejectUnexpectedSignature(bytes);
|
|
193
|
+
}
|
|
194
|
+
const env = compressionEnvelope(bytes, cfg);
|
|
195
|
+
if (env !== null) {
|
|
196
|
+
const { alg, payload, size } = env;
|
|
197
|
+
let out = callCodecSync(cfg.codecs, alg, 'decompress', [payload, size]);
|
|
198
|
+
if (out === undefined) {
|
|
199
|
+
const builtin = platform.decompress[alg];
|
|
200
|
+
if (builtin === undefined)
|
|
201
|
+
throw unavailable(alg, 'decompression', webFormat(alg, 'DecompressionStream') !== undefined);
|
|
202
|
+
out = builtin(payload, size);
|
|
203
|
+
}
|
|
204
|
+
bytes = checkSize(out, size, alg);
|
|
205
|
+
}
|
|
206
|
+
return decodeDocument(bytes, cfg);
|
|
207
|
+
}
|
|
208
|
+
// --- asynchronous API -------------------------------------------------------------------------
|
|
209
|
+
export async function encodeAsyncWith(value, options, platform) {
|
|
210
|
+
const cfg = encodeConfig(options);
|
|
211
|
+
let out = encodeDocument(value, cfg.maxDepth, cfg.autoTabular);
|
|
212
|
+
if (cfg.compress) {
|
|
213
|
+
const alg = cfg.algorithm;
|
|
214
|
+
const builtin = platform.compress[alg];
|
|
215
|
+
const format = webFormat(alg, 'CompressionStream');
|
|
216
|
+
if (!hasCodec(cfg.codecs, alg, 'compress') && format === undefined && builtin === undefined) {
|
|
217
|
+
throw unavailable(alg, 'compression', false);
|
|
218
|
+
}
|
|
219
|
+
if (out.length >= MIN_COMPRESSION_SIZE) {
|
|
220
|
+
if (out.length > 0xffffffff)
|
|
221
|
+
throw fail('size_limit', 'document too large to compress');
|
|
222
|
+
let payload = await callCodecAsync(cfg.codecs, alg, 'compress', [out]);
|
|
223
|
+
if (payload === undefined)
|
|
224
|
+
payload = format !== undefined ? await webCompress(format, out) : builtin(out);
|
|
225
|
+
out = envelopeIfSmaller(out, payload, alg);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (cfg.hmacKey !== undefined) {
|
|
229
|
+
const s = subtle();
|
|
230
|
+
const mac = s !== undefined ? await webHmacSign(s, cfg.hmacKey, concat2(SIGNED_HEADER, out)) : platform.hmac(cfg.hmacKey, [SIGNED_HEADER, out]);
|
|
231
|
+
out = assembleSigned(mac, out);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
export async function decodeAsyncWith(data, options, platform) {
|
|
236
|
+
const cfg = decodeConfig(options);
|
|
237
|
+
// A private copy: the caller may reuse its buffer while we wait.
|
|
238
|
+
let bytes = inputBytes(data).slice();
|
|
239
|
+
if (cfg.hmacKey !== undefined) {
|
|
240
|
+
const env = signedEnvelope(bytes);
|
|
241
|
+
const s = subtle();
|
|
242
|
+
const ok = s !== undefined
|
|
243
|
+
? await webHmacVerify(s, cfg.hmacKey, env.mac, concat2(env.header, env.payload))
|
|
244
|
+
: platform.equal(platform.hmac(cfg.hmacKey, [env.header, env.payload]), env.mac);
|
|
245
|
+
if (!ok)
|
|
246
|
+
throw fail('authentication_failed', 'HMAC verification failed');
|
|
247
|
+
bytes = env.payload;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
rejectUnexpectedSignature(bytes);
|
|
251
|
+
}
|
|
252
|
+
const env = compressionEnvelope(bytes, cfg);
|
|
253
|
+
if (env !== null) {
|
|
254
|
+
const { alg, payload, size } = env;
|
|
255
|
+
let out = await callCodecAsync(cfg.codecs, alg, 'decompress', [payload, size]);
|
|
256
|
+
if (out === undefined) {
|
|
257
|
+
const format = webFormat(alg, 'DecompressionStream');
|
|
258
|
+
const builtin = platform.decompress[alg];
|
|
259
|
+
if (format !== undefined)
|
|
260
|
+
out = await webDecompress(alg, format, payload, size);
|
|
261
|
+
else if (builtin !== undefined)
|
|
262
|
+
out = builtin(payload, size);
|
|
263
|
+
else
|
|
264
|
+
throw unavailable(alg, 'decompression', false);
|
|
265
|
+
}
|
|
266
|
+
bytes = checkSize(out, size, alg);
|
|
267
|
+
}
|
|
268
|
+
return decodeDocument(bytes, cfg);
|
|
269
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DecodeConfig } from './options.js';
|
|
2
|
+
/** A plain Uint8Array view of the same bytes (Buffer.prototype.slice does not copy). */
|
|
3
|
+
export declare function plainBytes(b: Uint8Array): Uint8Array;
|
|
4
|
+
/** Decodes one bare BTOON document (no envelopes). */
|
|
5
|
+
export declare function decodeDocument(bytes: Uint8Array, cfg: DecodeConfig): unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Used by the encoder: an `Ext` that carries a BTOON-defined id must hold a payload a
|
|
8
|
+
* strict decoder accepts, so the encoder never emits a document decoders reject.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateBtoonExt(type: number, data: Uint8Array, depth: number, maxDepth?: number): void;
|