@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
@@ -0,0 +1,943 @@
1
+ // Canonical BTOON encoder (SPEC §4–§5). Everything is appended to one growable buffer;
2
+ // tabular payloads and column lengths are back-patched.
3
+ import { EXT_BIGINT, EXT_F32_VECTOR, EXT_F64_VECTOR, EXT_TABULAR, MAX_OFFSET_MINUTES, NANOS_PER_SECOND, TABULAR_VERSION, } from './constants.js';
4
+ import { validateBtoonExt } from './decode.js';
5
+ import { fail, isStackOverflow } from './errors.js';
6
+ import { compareUtf8, isWellFormed, loneSurrogateError, textEncoder, utf8Length } from './utf8.js';
7
+ import { dateTime, isExt, isTimestamp } from './values.js';
8
+ const TWO_32 = 4294967296;
9
+ const TWO_34 = 17179869184;
10
+ const UINT64_MAX = (1n << 64n) - 1n;
11
+ const INT64_MIN = -(1n << 63n);
12
+ const INT63 = 1n << 63n;
13
+ const LITTLE_ENDIAN = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
14
+ // Object kinds, in the order the encoder tests for them.
15
+ const K_ARRAY = 1;
16
+ const K_PLAIN = 2;
17
+ const K_VIEW = 3;
18
+ const K_BUFFER = 4;
19
+ const K_DATE = 5;
20
+ const K_MAP = 6;
21
+ const K_TIMESTAMP = 7;
22
+ const K_EXT = 8;
23
+ const K_TOJSON = 9;
24
+ /** A Number, String, Boolean or BigInt wrapper object (unwrapped, as JSON.stringify does). */
25
+ const K_BOXED = 10;
26
+ /** Any other object: encoded like JSON.stringify, as a map of its own enumerable string keys. */
27
+ const K_OBJECT = 11;
28
+ const objectToString = Object.prototype.toString;
29
+ const NOT_BOXED = Symbol('not boxed');
30
+ const BOXES = new Map([
31
+ ['[object Number]', Number.prototype],
32
+ ['[object String]', String.prototype],
33
+ ['[object Boolean]', Boolean.prototype],
34
+ ['[object BigInt]', BigInt.prototype],
35
+ ]);
36
+ /** The primitive inside a wrapper object, checked by internal slot (a spoofed tag fails). */
37
+ function unbox(o, tag = objectToString.call(o)) {
38
+ const proto = BOXES.get(tag);
39
+ if (proto === undefined)
40
+ return NOT_BOXED;
41
+ try {
42
+ return proto.valueOf.call(o);
43
+ }
44
+ catch {
45
+ return NOT_BOXED;
46
+ }
47
+ }
48
+ function classify(o) {
49
+ if (Array.isArray(o))
50
+ return K_ARRAY;
51
+ const proto = Object.getPrototypeOf(o);
52
+ if (proto === Object.prototype || proto === null)
53
+ return K_PLAIN;
54
+ if (ArrayBuffer.isView(o))
55
+ return K_VIEW;
56
+ if (o instanceof Date)
57
+ return K_DATE;
58
+ if (o instanceof Map)
59
+ return K_MAP;
60
+ if (isTimestamp(o))
61
+ return K_TIMESTAMP;
62
+ if (isExt(o))
63
+ return K_EXT;
64
+ const tag = objectToString.call(o);
65
+ if (tag === '[object ArrayBuffer]' || tag === '[object SharedArrayBuffer]')
66
+ return K_BUFFER;
67
+ if (tag === '[object Date]')
68
+ return K_DATE; // from another realm
69
+ if (tag === '[object Map]')
70
+ return K_MAP; // from another realm
71
+ if (typeof o.toJSON === 'function')
72
+ return K_TOJSON;
73
+ if (unbox(o, tag) !== NOT_BOXED)
74
+ return K_BOXED;
75
+ return K_OBJECT; // class instances, and plain objects from another realm
76
+ }
77
+ function maxDepthError() {
78
+ return fail('max_depth', 'maximum nesting depth exceeded (or the value is cyclic)');
79
+ }
80
+ /** Keys of a value that encodes as a map, or null. Non-string Map keys → null. */
81
+ function mapKeys(v, kind) {
82
+ if (kind === K_PLAIN || kind === K_OBJECT)
83
+ return Object.keys(v);
84
+ if (kind === K_MAP) {
85
+ const keys = [];
86
+ for (const k of Map.prototype.keys.call(v)) {
87
+ if (typeof k !== 'string')
88
+ return null;
89
+ keys.push(k);
90
+ }
91
+ return keys;
92
+ }
93
+ return null;
94
+ }
95
+ function kindOf(v) {
96
+ return typeof v === 'object' && v !== null ? classify(v) : 0;
97
+ }
98
+ function callToJSON(o) {
99
+ return o.toJSON();
100
+ }
101
+ function cell(row, kind, key) {
102
+ return kind === K_MAP ? Map.prototype.get.call(row, key) : row[key];
103
+ }
104
+ /** SPEC §5.6.1 type code of a (resolved) value: the logical type of the first row's cell. */
105
+ function typeCode(v) {
106
+ switch (typeof v) {
107
+ case 'undefined':
108
+ return 0;
109
+ case 'boolean':
110
+ return 1;
111
+ case 'number':
112
+ return Number.isSafeInteger(v) && (v !== 0 || 1 / v > 0) ? 2 : 4;
113
+ case 'bigint':
114
+ if (v >= INT64_MIN && v < INT63)
115
+ return 2;
116
+ if (v >= INT63 && v <= UINT64_MAX)
117
+ return 3;
118
+ return 9;
119
+ case 'string':
120
+ return 5;
121
+ case 'object': {
122
+ if (v === null)
123
+ return 0;
124
+ switch (classify(v)) {
125
+ case K_ARRAY:
126
+ return 7;
127
+ case K_PLAIN:
128
+ case K_MAP:
129
+ case K_OBJECT:
130
+ case K_TOJSON: // a toJSON() result that has toJSON() itself: encoded by its own properties
131
+ return 8;
132
+ case K_BOXED:
133
+ return typeCode(unbox(v));
134
+ case K_EXT:
135
+ // A raw tabular extension is logically an array (its encoding starts like one).
136
+ return v.type === EXT_TABULAR ? 7 : 9;
137
+ case K_BUFFER:
138
+ return 6;
139
+ case K_VIEW: {
140
+ const tag = v[Symbol.toStringTag];
141
+ return tag === 'Float32Array' || tag === 'Float64Array' ? 9 : 6;
142
+ }
143
+ default:
144
+ return 9;
145
+ }
146
+ }
147
+ default:
148
+ return 9;
149
+ }
150
+ }
151
+ /** Minimal two's-complement big-endian bytes (SPEC §5.4). */
152
+ function bigintBytes(v) {
153
+ let hex;
154
+ if (v >= 0n) {
155
+ hex = v.toString(16);
156
+ if (hex.length & 1)
157
+ hex = '0' + hex;
158
+ if (parseInt(hex.slice(0, 2), 16) >= 0x80)
159
+ hex = '00' + hex;
160
+ }
161
+ else {
162
+ const bytes = ((-v).toString(16).length >> 1) + 2;
163
+ hex = ((1n << BigInt(bytes * 8)) + v).toString(16);
164
+ if (hex.length & 1)
165
+ hex = '0' + hex;
166
+ while (hex.length > 2 && hex.charCodeAt(0) === 0x66 && hex.charCodeAt(1) === 0x66 && parseInt(hex[2], 16) >= 8) {
167
+ hex = hex.slice(2);
168
+ }
169
+ }
170
+ const out = new Uint8Array(hex.length >> 1);
171
+ for (let i = 0; i < out.length; i++)
172
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
173
+ return out;
174
+ }
175
+ class Encoder {
176
+ constructor(buf, maxDepth, autoTabular) {
177
+ this.pos = 0;
178
+ // Out-parameters of planTabular() when it declines.
179
+ this.items = [];
180
+ this.resolvedUpTo = 0;
181
+ this.buf = buf;
182
+ this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
183
+ this.maxDepth = maxDepth;
184
+ this.autoTabular = autoTabular;
185
+ }
186
+ // --- buffer --------------------------------------------------------------------
187
+ ensure(n) {
188
+ if (this.pos + n > this.buf.length)
189
+ this.grow(n);
190
+ }
191
+ grow(n) {
192
+ const need = this.pos + n;
193
+ let cap = this.buf.length * 2;
194
+ if (cap < need)
195
+ cap = need;
196
+ const next = new Uint8Array(cap);
197
+ next.set(this.buf.subarray(0, this.pos));
198
+ this.buf = next;
199
+ this.view = new DataView(next.buffer);
200
+ }
201
+ u8(b) {
202
+ this.ensure(1);
203
+ this.buf[this.pos++] = b;
204
+ }
205
+ bytes(b) {
206
+ this.ensure(b.length);
207
+ this.buf.set(b, this.pos);
208
+ this.pos += b.length;
209
+ }
210
+ // --- dispatch ------------------------------------------------------------------
211
+ value(v, depth) {
212
+ this.any(v, depth, true);
213
+ }
214
+ /** Encodes a value that already went through toJSON(): no second toJSON() call. */
215
+ resolved(v, depth) {
216
+ this.any(v, depth, false);
217
+ }
218
+ any(v, depth, allowToJSON) {
219
+ switch (typeof v) {
220
+ case 'string':
221
+ this.string(v);
222
+ return;
223
+ case 'number':
224
+ this.number(v);
225
+ return;
226
+ case 'boolean':
227
+ this.u8(v ? 0xc3 : 0xc2);
228
+ return;
229
+ case 'object':
230
+ if (v === null)
231
+ this.u8(0xc0);
232
+ else
233
+ this.object(v, classify(v), depth, allowToJSON);
234
+ return;
235
+ case 'undefined':
236
+ this.u8(0xc0);
237
+ return;
238
+ case 'bigint':
239
+ this.bigint(v);
240
+ return;
241
+ default:
242
+ throw fail('invalid_argument', `cannot encode a ${typeof v}`);
243
+ }
244
+ }
245
+ object(o, kind, depth, allowToJSON) {
246
+ switch (kind) {
247
+ case K_ARRAY:
248
+ this.array(o, depth);
249
+ return;
250
+ case K_PLAIN:
251
+ this.plainMap(o, depth);
252
+ return;
253
+ case K_VIEW:
254
+ this.arrayBufferView(o);
255
+ return;
256
+ case K_BUFFER:
257
+ this.binary(new Uint8Array(o));
258
+ return;
259
+ case K_DATE:
260
+ this.timestampMs(dateTime(o));
261
+ return;
262
+ case K_MAP:
263
+ this.mapObject(o, depth);
264
+ return;
265
+ case K_TIMESTAMP:
266
+ this.timestamp(o);
267
+ return;
268
+ case K_EXT:
269
+ this.ext(o, depth);
270
+ return;
271
+ case K_TOJSON:
272
+ // Like JSON.stringify, toJSON() is called once per value: a result that has its own
273
+ // toJSON() is encoded by its own enumerable properties.
274
+ if (allowToJSON)
275
+ this.resolved(callToJSON(o), depth);
276
+ else
277
+ this.plainMap(o, depth);
278
+ return;
279
+ case K_BOXED:
280
+ this.any(unbox(o), depth, allowToJSON);
281
+ return;
282
+ default: // K_OBJECT
283
+ this.plainMap(o, depth);
284
+ }
285
+ }
286
+ // --- scalars -------------------------------------------------------------------
287
+ number(v) {
288
+ // -0 is kept as a float so that its sign survives (SPEC §2).
289
+ if (Number.isSafeInteger(v) && (v !== 0 || 1 / v > 0)) {
290
+ this.int(v);
291
+ return;
292
+ }
293
+ this.ensure(9);
294
+ const p = this.pos;
295
+ this.buf[p] = 0xcb;
296
+ if (v !== v) {
297
+ this.view.setUint32(p + 1, 0x7ff80000);
298
+ this.view.setUint32(p + 5, 0);
299
+ }
300
+ else {
301
+ this.view.setFloat64(p + 1, v);
302
+ }
303
+ this.pos = p + 9;
304
+ }
305
+ /** Smallest integer format for a safe integer (SPEC §4.1). */
306
+ int(v) {
307
+ this.ensure(9);
308
+ const buf = this.buf;
309
+ const p = this.pos;
310
+ if (v >= 0) {
311
+ if (v < 0x80) {
312
+ buf[p] = v;
313
+ this.pos = p + 1;
314
+ }
315
+ else if (v < 0x100) {
316
+ buf[p] = 0xcc;
317
+ buf[p + 1] = v;
318
+ this.pos = p + 2;
319
+ }
320
+ else if (v < 0x10000) {
321
+ buf[p] = 0xcd;
322
+ this.view.setUint16(p + 1, v);
323
+ this.pos = p + 3;
324
+ }
325
+ else if (v < TWO_32) {
326
+ buf[p] = 0xce;
327
+ this.view.setUint32(p + 1, v);
328
+ this.pos = p + 5;
329
+ }
330
+ else {
331
+ buf[p] = 0xcf;
332
+ this.view.setUint32(p + 1, Math.floor(v / TWO_32));
333
+ this.view.setUint32(p + 5, v >>> 0);
334
+ this.pos = p + 9;
335
+ }
336
+ }
337
+ else if (v >= -32) {
338
+ buf[p] = v & 0xff;
339
+ this.pos = p + 1;
340
+ }
341
+ else if (v >= -0x80) {
342
+ buf[p] = 0xd0;
343
+ buf[p + 1] = v & 0xff;
344
+ this.pos = p + 2;
345
+ }
346
+ else if (v >= -0x8000) {
347
+ buf[p] = 0xd1;
348
+ this.view.setInt16(p + 1, v);
349
+ this.pos = p + 3;
350
+ }
351
+ else if (v >= -0x80000000) {
352
+ buf[p] = 0xd2;
353
+ this.view.setInt32(p + 1, v);
354
+ this.pos = p + 5;
355
+ }
356
+ else {
357
+ buf[p] = 0xd3;
358
+ this.view.setInt32(p + 1, Math.floor(v / TWO_32));
359
+ this.view.setUint32(p + 5, v >>> 0);
360
+ this.pos = p + 9;
361
+ }
362
+ }
363
+ bigint(v) {
364
+ if (v >= -0x80000000n && v <= 0xffffffffn) {
365
+ this.int(Number(v));
366
+ return;
367
+ }
368
+ if (v >= 0n ? v <= UINT64_MAX : v >= INT64_MIN) {
369
+ this.ensure(9);
370
+ const p = this.pos;
371
+ if (v >= 0n) {
372
+ this.buf[p] = 0xcf;
373
+ this.view.setBigUint64(p + 1, v);
374
+ }
375
+ else {
376
+ this.buf[p] = 0xd3;
377
+ this.view.setBigInt64(p + 1, v);
378
+ }
379
+ this.pos = p + 9;
380
+ return;
381
+ }
382
+ const b = bigintBytes(v);
383
+ this.extHeader(EXT_BIGINT, b.length);
384
+ this.bytes(b);
385
+ }
386
+ string(s) {
387
+ const len = s.length;
388
+ if (len < 32) {
389
+ // Short strings: optimistic ASCII pass straight into a fixstr.
390
+ this.ensure(2 + len * 3);
391
+ const buf = this.buf;
392
+ const start = this.pos;
393
+ let p = start + 1;
394
+ let i = 0;
395
+ for (; i < len; i++) {
396
+ const c = s.charCodeAt(i);
397
+ if (c >= 0x80)
398
+ break;
399
+ buf[p++] = c;
400
+ }
401
+ if (i === len) {
402
+ buf[start] = 0xa0 | len;
403
+ this.pos = p;
404
+ return;
405
+ }
406
+ const n = utf8Length(s);
407
+ if (n <= 31) {
408
+ buf[start] = 0xa0 | n;
409
+ this.pos = start + 1;
410
+ }
411
+ else {
412
+ buf[start] = 0xd9;
413
+ buf[start + 1] = n;
414
+ this.pos = start + 2;
415
+ }
416
+ this.utf8(s);
417
+ return;
418
+ }
419
+ if (!isWellFormed(s))
420
+ throw loneSurrogateError();
421
+ const max = len * 3;
422
+ const hdr = max <= 0xff ? 2 : max <= 0xffff ? 3 : 5;
423
+ this.ensure(hdr + max);
424
+ const start = this.pos;
425
+ const { written } = textEncoder.encodeInto(s, this.buf.subarray(start + hdr, start + hdr + max));
426
+ const n = written;
427
+ if (n > 0xffffffff)
428
+ throw fail('size_limit', 'string longer than 2^32-1 bytes');
429
+ const need = n <= 31 ? 1 : n <= 0xff ? 2 : n <= 0xffff ? 3 : 5;
430
+ if (need !== hdr)
431
+ this.buf.copyWithin(start + need, start + hdr, start + hdr + n);
432
+ this.strHeaderAt(start, n, need);
433
+ this.pos = start + need + n;
434
+ }
435
+ strHeaderAt(p, n, size) {
436
+ const buf = this.buf;
437
+ if (size === 1) {
438
+ buf[p] = 0xa0 | n;
439
+ }
440
+ else if (size === 2) {
441
+ buf[p] = 0xd9;
442
+ buf[p + 1] = n;
443
+ }
444
+ else if (size === 3) {
445
+ buf[p] = 0xda;
446
+ this.view.setUint16(p + 1, n);
447
+ }
448
+ else {
449
+ buf[p] = 0xdb;
450
+ this.view.setUint32(p + 1, n);
451
+ }
452
+ }
453
+ /** Writes the UTF-8 bytes of a well-formed string; the caller reserved the space. */
454
+ utf8(s) {
455
+ const buf = this.buf;
456
+ let p = this.pos;
457
+ for (let i = 0; i < s.length; i++) {
458
+ let c = s.charCodeAt(i);
459
+ if (c < 0x80) {
460
+ buf[p++] = c;
461
+ }
462
+ else if (c < 0x800) {
463
+ buf[p++] = 0xc0 | (c >> 6);
464
+ buf[p++] = 0x80 | (c & 0x3f);
465
+ }
466
+ else if (c < 0xd800 || c > 0xdfff) {
467
+ buf[p++] = 0xe0 | (c >> 12);
468
+ buf[p++] = 0x80 | ((c >> 6) & 0x3f);
469
+ buf[p++] = 0x80 | (c & 0x3f);
470
+ }
471
+ else {
472
+ c = 0x10000 + ((c - 0xd800) << 10) + (s.charCodeAt(++i) - 0xdc00);
473
+ buf[p++] = 0xf0 | (c >> 18);
474
+ buf[p++] = 0x80 | ((c >> 12) & 0x3f);
475
+ buf[p++] = 0x80 | ((c >> 6) & 0x3f);
476
+ buf[p++] = 0x80 | (c & 0x3f);
477
+ }
478
+ }
479
+ this.pos = p;
480
+ }
481
+ /** Raw UTF-8 bytes of a string whose exact byte length `n` is known. */
482
+ utf8Exact(s, n) {
483
+ this.ensure(n);
484
+ if (n < 64) {
485
+ this.utf8(s);
486
+ }
487
+ else {
488
+ textEncoder.encodeInto(s, this.buf.subarray(this.pos, this.pos + n));
489
+ this.pos += n;
490
+ }
491
+ }
492
+ binary(b) {
493
+ const n = b.length;
494
+ this.ensure(5);
495
+ const p = this.pos;
496
+ if (n <= 0xff) {
497
+ this.buf[p] = 0xc4;
498
+ this.buf[p + 1] = n;
499
+ this.pos = p + 2;
500
+ }
501
+ else if (n <= 0xffff) {
502
+ this.buf[p] = 0xc5;
503
+ this.view.setUint16(p + 1, n);
504
+ this.pos = p + 3;
505
+ }
506
+ else if (n <= 0xffffffff) {
507
+ this.buf[p] = 0xc6;
508
+ this.view.setUint32(p + 1, n);
509
+ this.pos = p + 5;
510
+ }
511
+ else {
512
+ throw fail('size_limit', 'binary longer than 2^32-1 bytes');
513
+ }
514
+ this.bytes(b);
515
+ }
516
+ arrayBufferView(v) {
517
+ const tag = v[Symbol.toStringTag];
518
+ const raw = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
519
+ if (tag === 'Float32Array' || tag === 'Float64Array') {
520
+ // SPEC §5.5: little-endian payload, NaN bits untouched.
521
+ const n = raw.length;
522
+ this.extHeader(tag === 'Float32Array' ? EXT_F32_VECTOR : EXT_F64_VECTOR, n);
523
+ this.ensure(n);
524
+ if (LITTLE_ENDIAN) {
525
+ this.buf.set(raw, this.pos);
526
+ }
527
+ else {
528
+ const size = tag === 'Float32Array' ? 4 : 8;
529
+ for (let i = 0; i < n; i += size) {
530
+ for (let b = 0; b < size; b++)
531
+ this.buf[this.pos + i + b] = raw[i + size - 1 - b];
532
+ }
533
+ }
534
+ this.pos += n;
535
+ return;
536
+ }
537
+ this.binary(raw);
538
+ }
539
+ extHeader(type, n) {
540
+ this.ensure(6);
541
+ const buf = this.buf;
542
+ const p = this.pos;
543
+ const t = type & 0xff;
544
+ let fixed = 0;
545
+ switch (n) {
546
+ case 1:
547
+ fixed = 0xd4;
548
+ break;
549
+ case 2:
550
+ fixed = 0xd5;
551
+ break;
552
+ case 4:
553
+ fixed = 0xd6;
554
+ break;
555
+ case 8:
556
+ fixed = 0xd7;
557
+ break;
558
+ case 16:
559
+ fixed = 0xd8;
560
+ break;
561
+ }
562
+ if (fixed !== 0) {
563
+ buf[p] = fixed;
564
+ buf[p + 1] = t;
565
+ this.pos = p + 2;
566
+ }
567
+ else if (n <= 0xff) {
568
+ buf[p] = 0xc7;
569
+ buf[p + 1] = n;
570
+ buf[p + 2] = t;
571
+ this.pos = p + 3;
572
+ }
573
+ else if (n <= 0xffff) {
574
+ buf[p] = 0xc8;
575
+ this.view.setUint16(p + 1, n);
576
+ buf[p + 3] = t;
577
+ this.pos = p + 4;
578
+ }
579
+ else if (n <= 0xffffffff) {
580
+ buf[p] = 0xc9;
581
+ this.view.setUint32(p + 1, n);
582
+ buf[p + 5] = t;
583
+ this.pos = p + 6;
584
+ }
585
+ else {
586
+ throw fail('size_limit', 'extension payload longer than 2^32-1 bytes');
587
+ }
588
+ }
589
+ ext(e, depth) {
590
+ const type = e.type;
591
+ const data = e.data;
592
+ if (typeof type !== 'number' || !Number.isInteger(type) || type < -128 || type > 127) {
593
+ throw fail('invalid_argument', 'Ext type must be an integer in [-128, 127]');
594
+ }
595
+ if (!ArrayBuffer.isView(data))
596
+ throw fail('invalid_argument', 'Ext data must be a Uint8Array');
597
+ validateBtoonExt(type, data, depth, this.maxDepth);
598
+ this.extHeader(type, data.byteLength);
599
+ this.bytes(data);
600
+ }
601
+ // --- timestamps (SPEC §5.1) -------------------------------------------------------
602
+ timestampMs(ms) {
603
+ const sec = Math.floor(ms / 1000);
604
+ this.timestampParts(sec, (ms - sec * 1000) * 1e6);
605
+ }
606
+ timestamp(t) {
607
+ const sec = t.seconds;
608
+ const ns = t.nanoseconds;
609
+ const off = t.offsetMinutes;
610
+ if (typeof sec !== 'bigint' || sec < INT64_MIN || sec >= INT63) {
611
+ throw fail('invalid_argument', 'Timestamp seconds must be a bigint in the int64 range');
612
+ }
613
+ if (typeof ns !== 'number' || !Number.isInteger(ns) || ns < 0 || ns >= NANOS_PER_SECOND) {
614
+ throw fail('invalid_argument', 'Timestamp nanoseconds must be an integer in [0, 999999999]');
615
+ }
616
+ if (off !== undefined) {
617
+ if (typeof off !== 'number' || !Number.isInteger(off) || off < -MAX_OFFSET_MINUTES || off > MAX_OFFSET_MINUTES) {
618
+ throw fail('invalid_argument', 'Timestamp offsetMinutes must be an integer in [-1080, 1080]');
619
+ }
620
+ this.ensure(17);
621
+ const p = this.pos;
622
+ this.buf[p] = 0xc7;
623
+ this.buf[p + 1] = 14;
624
+ this.buf[p + 2] = 0xff;
625
+ this.view.setUint32(p + 3, ns);
626
+ this.view.setBigInt64(p + 7, sec);
627
+ this.view.setInt16(p + 15, off);
628
+ this.pos = p + 17;
629
+ return;
630
+ }
631
+ if (sec >= 0n && sec < 17179869184n) {
632
+ this.timestampParts(Number(sec), ns);
633
+ return;
634
+ }
635
+ this.ensure(15);
636
+ const p = this.pos;
637
+ this.buf[p] = 0xc7;
638
+ this.buf[p + 1] = 12;
639
+ this.buf[p + 2] = 0xff;
640
+ this.view.setUint32(p + 3, ns);
641
+ this.view.setBigInt64(p + 7, sec);
642
+ this.pos = p + 15;
643
+ }
644
+ /** MessagePack timestamp selection for a UTC instant; `sec` is a safe integer. */
645
+ timestampParts(sec, ns) {
646
+ this.ensure(15);
647
+ const buf = this.buf;
648
+ const p = this.pos;
649
+ if (sec >= 0 && sec < TWO_34) {
650
+ if (ns === 0 && sec < TWO_32) {
651
+ buf[p] = 0xd6;
652
+ buf[p + 1] = 0xff;
653
+ this.view.setUint32(p + 2, sec);
654
+ this.pos = p + 6;
655
+ }
656
+ else {
657
+ buf[p] = 0xd7;
658
+ buf[p + 1] = 0xff;
659
+ this.view.setUint32(p + 2, ns * 4 + Math.floor(sec / TWO_32));
660
+ this.view.setUint32(p + 6, sec >>> 0);
661
+ this.pos = p + 10;
662
+ }
663
+ return;
664
+ }
665
+ buf[p] = 0xc7;
666
+ buf[p + 1] = 12;
667
+ buf[p + 2] = 0xff;
668
+ this.view.setUint32(p + 3, ns);
669
+ this.view.setInt32(p + 7, Math.floor(sec / TWO_32));
670
+ this.view.setUint32(p + 11, sec >>> 0);
671
+ this.pos = p + 15;
672
+ }
673
+ // --- containers ------------------------------------------------------------------
674
+ arrayHeader(n) {
675
+ this.ensure(5);
676
+ const p = this.pos;
677
+ if (n <= 15) {
678
+ this.buf[p] = 0x90 | n;
679
+ this.pos = p + 1;
680
+ }
681
+ else if (n <= 0xffff) {
682
+ this.buf[p] = 0xdc;
683
+ this.view.setUint16(p + 1, n);
684
+ this.pos = p + 3;
685
+ }
686
+ else {
687
+ this.buf[p] = 0xdd;
688
+ this.view.setUint32(p + 1, n);
689
+ this.pos = p + 5;
690
+ }
691
+ }
692
+ mapHeader(n) {
693
+ this.ensure(5);
694
+ const p = this.pos;
695
+ if (n <= 15) {
696
+ this.buf[p] = 0x80 | n;
697
+ this.pos = p + 1;
698
+ }
699
+ else if (n <= 0xffff) {
700
+ this.buf[p] = 0xde;
701
+ this.view.setUint16(p + 1, n);
702
+ this.pos = p + 3;
703
+ }
704
+ else {
705
+ this.buf[p] = 0xdf;
706
+ this.view.setUint32(p + 1, n);
707
+ this.pos = p + 5;
708
+ }
709
+ }
710
+ plainMap(o, depth) {
711
+ if (depth >= this.maxDepth)
712
+ throw maxDepthError();
713
+ const keys = Object.keys(o);
714
+ const n = keys.length;
715
+ if (n > 1)
716
+ keys.sort(compareUtf8);
717
+ this.mapHeader(n);
718
+ for (let i = 0; i < n; i++) {
719
+ const k = keys[i];
720
+ this.string(k);
721
+ this.value(o[k], depth + 1);
722
+ }
723
+ }
724
+ mapObject(m, depth) {
725
+ if (depth >= this.maxDepth)
726
+ throw maxDepthError();
727
+ const keys = [];
728
+ for (const k of Map.prototype.keys.call(m)) {
729
+ if (typeof k !== 'string')
730
+ throw fail('invalid_argument', 'Map keys must be strings');
731
+ keys.push(k);
732
+ }
733
+ const n = keys.length;
734
+ if (n > 1)
735
+ keys.sort(compareUtf8);
736
+ this.mapHeader(n);
737
+ for (let i = 0; i < n; i++) {
738
+ const k = keys[i];
739
+ this.string(k);
740
+ this.value(Map.prototype.get.call(m, k), depth + 1);
741
+ }
742
+ }
743
+ array(arr, depth) {
744
+ if (depth >= this.maxDepth)
745
+ throw maxDepthError();
746
+ const n = arr.length;
747
+ let items = arr;
748
+ let resolvedUpTo = 0;
749
+ if (this.autoTabular && n >= 2) {
750
+ const plan = this.planTabular(arr);
751
+ if (plan !== null) {
752
+ this.tabular(plan, depth);
753
+ return;
754
+ }
755
+ items = this.items;
756
+ resolvedUpTo = this.resolvedUpTo;
757
+ this.items = [];
758
+ }
759
+ this.arrayHeader(n);
760
+ for (let i = 0; i < n; i++) {
761
+ if (i < resolvedUpTo)
762
+ this.resolved(items[i], depth + 1);
763
+ else
764
+ this.value(arr[i], depth + 1);
765
+ }
766
+ }
767
+ /**
768
+ * SPEC §5.6.2: qualification and size rule. Elements with toJSON() are resolved here
769
+ * (once); when the array is not tabular, `items[0..resolvedUpTo)` holds the elements
770
+ * already resolved so the plain path doesn't call toJSON() twice.
771
+ */
772
+ planTabular(arr) {
773
+ const n = arr.length;
774
+ let resolved = null;
775
+ let kinds = null; // allocated once the first element qualifies
776
+ let firstKeys = [];
777
+ let keySet = null;
778
+ for (let i = 0; i < n; i++) {
779
+ let row = arr[i];
780
+ let kind = kindOf(row);
781
+ if (kind === K_TOJSON) {
782
+ if (resolved === null)
783
+ resolved = arr.slice(0, i);
784
+ row = callToJSON(row);
785
+ kind = kindOf(row);
786
+ if (kind === K_TOJSON)
787
+ kind = K_OBJECT; // no second toJSON()
788
+ }
789
+ if (resolved !== null)
790
+ resolved[i] = row;
791
+ const keys = mapKeys(row, kind);
792
+ let ok = keys !== null;
793
+ if (ok) {
794
+ if (i === 0) {
795
+ firstKeys = keys;
796
+ ok = firstKeys.length > 0;
797
+ }
798
+ else if (keys.length !== firstKeys.length) {
799
+ ok = false;
800
+ }
801
+ else {
802
+ for (let k = 0; k < keys.length; k++) {
803
+ if (keys[k] !== firstKeys[k]) {
804
+ if (keySet === null)
805
+ keySet = new Set(firstKeys);
806
+ for (let j = 0; j < keys.length; j++) {
807
+ if (!keySet.has(keys[j])) {
808
+ ok = false;
809
+ break;
810
+ }
811
+ }
812
+ break;
813
+ }
814
+ }
815
+ }
816
+ }
817
+ if (!ok)
818
+ return this.decline(arr, resolved, i + 1);
819
+ if (kinds === null)
820
+ kinds = new Uint8Array(n);
821
+ kinds[i] = kind;
822
+ }
823
+ const keys = firstKeys.slice().sort(compareUtf8);
824
+ const c = keys.length;
825
+ const lens = new Array(c);
826
+ let perRow = c <= 15 ? 1 : c <= 0xffff ? 3 : 5;
827
+ let overhead = 18;
828
+ for (let k = 0; k < c; k++) {
829
+ const len = utf8Length(keys[k]);
830
+ lens[k] = len;
831
+ perRow += (len <= 31 ? 1 : len <= 0xff ? 2 : len <= 0xffff ? 3 : 5) + len;
832
+ overhead += 9 + len;
833
+ }
834
+ // N * perRow > overhead <=> perRow > floor(overhead / N)
835
+ if (perRow <= Math.floor(overhead / n))
836
+ return this.decline(arr, resolved, n);
837
+ return { rows: resolved ?? arr, kinds: kinds, keys, lens };
838
+ }
839
+ decline(arr, resolved, upTo) {
840
+ this.items = resolved ?? arr;
841
+ this.resolvedUpTo = resolved === null ? 0 : upTo;
842
+ return null;
843
+ }
844
+ tabular(plan, depth) {
845
+ if (depth + 1 >= this.maxDepth)
846
+ throw maxDepthError();
847
+ const { rows, kinds, keys, lens } = plan;
848
+ const n = rows.length;
849
+ const c = keys.length;
850
+ const start = this.pos;
851
+ this.ensure(6 + 12);
852
+ this.pos += 6; // room for the largest ext header; shrunk below
853
+ const payload = this.pos;
854
+ this.view.setUint32(payload, TABULAR_VERSION);
855
+ this.view.setUint32(payload + 4, c);
856
+ this.view.setUint32(payload + 8, n);
857
+ this.pos += 12;
858
+ for (let k = 0; k < c; k++) {
859
+ this.ensure(4);
860
+ this.view.setUint32(this.pos, lens[k]);
861
+ this.pos += 4;
862
+ this.utf8Exact(keys[k], lens[k]);
863
+ }
864
+ // Type codes describe the first row; resolve its cells once and reuse them below.
865
+ const first = rows[0];
866
+ const firstKind = kinds[0];
867
+ const firstCells = new Array(c);
868
+ const firstResolved = new Uint8Array(c);
869
+ this.ensure(c);
870
+ for (let k = 0; k < c; k++) {
871
+ let v = cell(first, firstKind, keys[k]);
872
+ if (kindOf(v) === K_TOJSON) {
873
+ v = callToJSON(v);
874
+ firstResolved[k] = 1;
875
+ }
876
+ firstCells[k] = v;
877
+ this.buf[this.pos++] = typeCode(v);
878
+ }
879
+ for (let k = 0; k < c; k++) {
880
+ const key = keys[k];
881
+ this.ensure(4);
882
+ const lenPos = this.pos;
883
+ this.pos += 4;
884
+ if (firstResolved[k] === 1)
885
+ this.resolved(firstCells[k], depth + 2);
886
+ else
887
+ this.value(firstCells[k], depth + 2);
888
+ for (let r = 1; r < n; r++)
889
+ this.value(cell(rows[r], kinds[r], key), depth + 2);
890
+ const colLen = this.pos - lenPos - 4;
891
+ if (colLen > 0xffffffff)
892
+ throw fail('size_limit', 'tabular column larger than 2^32-1 bytes');
893
+ this.view.setUint32(lenPos, colLen);
894
+ }
895
+ const len = this.pos - payload;
896
+ if (len > 0xffffffff)
897
+ throw fail('size_limit', 'tabular payload larger than 2^32-1 bytes');
898
+ const hdr = len <= 0xff ? 3 : len <= 0xffff ? 4 : 6;
899
+ if (hdr < 6) {
900
+ this.buf.copyWithin(start + hdr, payload, this.pos);
901
+ this.pos -= 6 - hdr;
902
+ }
903
+ const buf = this.buf;
904
+ if (hdr === 3) {
905
+ buf[start] = 0xc7;
906
+ buf[start + 1] = len;
907
+ buf[start + 2] = EXT_TABULAR & 0xff;
908
+ }
909
+ else if (hdr === 4) {
910
+ buf[start] = 0xc8;
911
+ this.view.setUint16(start + 1, len);
912
+ buf[start + 3] = EXT_TABULAR & 0xff;
913
+ }
914
+ else {
915
+ buf[start] = 0xc9;
916
+ this.view.setUint32(start + 1, len);
917
+ buf[start + 5] = EXT_TABULAR & 0xff;
918
+ }
919
+ }
920
+ }
921
+ let scratch = null;
922
+ const SCRATCH_MAX = 1 << 20;
923
+ /** Canonical encoding of `value` (no envelopes). */
924
+ export function encodeDocument(value, maxDepth, autoTabular) {
925
+ // Reuse one scratch buffer across calls; a re-entrant call (from toJSON) gets its own.
926
+ const buf = scratch ?? new Uint8Array(1024);
927
+ scratch = null;
928
+ const enc = new Encoder(buf, maxDepth, autoTabular);
929
+ try {
930
+ enc.value(value, 0);
931
+ }
932
+ catch (e) {
933
+ if (isStackOverflow(e)) {
934
+ throw fail('max_depth', 'value nests too deeply for the JavaScript call stack', e);
935
+ }
936
+ throw e;
937
+ }
938
+ finally {
939
+ if (enc.buf.length <= SCRATCH_MAX)
940
+ scratch = enc.buf;
941
+ }
942
+ return enc.buf.slice(0, enc.pos);
943
+ }