@elaraai/east 0.0.1-beta.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.
- package/LICENSE.md +682 -0
- package/README.md +276 -0
- package/dist/src/analyze.d.ts +95 -0
- package/dist/src/analyze.d.ts.map +1 -0
- package/dist/src/analyze.js +1110 -0
- package/dist/src/analyze.js.map +1 -0
- package/dist/src/ast.d.ts +263 -0
- package/dist/src/ast.d.ts.map +1 -0
- package/dist/src/ast.js +151 -0
- package/dist/src/ast.js.map +1 -0
- package/dist/src/ast_to_ir.d.ts +24 -0
- package/dist/src/ast_to_ir.d.ts.map +1 -0
- package/dist/src/ast_to_ir.js +834 -0
- package/dist/src/ast_to_ir.js.map +1 -0
- package/dist/src/builtins.d.ts +18 -0
- package/dist/src/builtins.d.ts.map +1 -0
- package/dist/src/builtins.js +1105 -0
- package/dist/src/builtins.js.map +1 -0
- package/dist/src/comparison.d.ts +28 -0
- package/dist/src/comparison.d.ts.map +1 -0
- package/dist/src/comparison.js +1017 -0
- package/dist/src/comparison.js.map +1 -0
- package/dist/src/compile.d.ts +22 -0
- package/dist/src/compile.d.ts.map +1 -0
- package/dist/src/compile.js +3260 -0
- package/dist/src/compile.js.map +1 -0
- package/dist/src/containers/ref.d.ts +106 -0
- package/dist/src/containers/ref.d.ts.map +1 -0
- package/dist/src/containers/ref.js +100 -0
- package/dist/src/containers/ref.js.map +1 -0
- package/dist/src/containers/sortedmap.d.ts +165 -0
- package/dist/src/containers/sortedmap.d.ts.map +1 -0
- package/dist/src/containers/sortedmap.js +237 -0
- package/dist/src/containers/sortedmap.js.map +1 -0
- package/dist/src/containers/sortedset.d.ts +185 -0
- package/dist/src/containers/sortedset.d.ts.map +1 -0
- package/dist/src/containers/sortedset.js +312 -0
- package/dist/src/containers/sortedset.js.map +1 -0
- package/dist/src/containers/variant.d.ts +131 -0
- package/dist/src/containers/variant.d.ts.map +1 -0
- package/dist/src/containers/variant.js +68 -0
- package/dist/src/containers/variant.js.map +1 -0
- package/dist/src/datetime_format/parse.d.ts +50 -0
- package/dist/src/datetime_format/parse.d.ts.map +1 -0
- package/dist/src/datetime_format/parse.js +908 -0
- package/dist/src/datetime_format/parse.js.map +1 -0
- package/dist/src/datetime_format/print.d.ts +35 -0
- package/dist/src/datetime_format/print.d.ts.map +1 -0
- package/dist/src/datetime_format/print.js +157 -0
- package/dist/src/datetime_format/print.js.map +1 -0
- package/dist/src/datetime_format/tokenize.d.ts +76 -0
- package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
- package/dist/src/datetime_format/tokenize.js +271 -0
- package/dist/src/datetime_format/tokenize.js.map +1 -0
- package/dist/src/datetime_format/types.d.ts +99 -0
- package/dist/src/datetime_format/types.d.ts.map +1 -0
- package/dist/src/datetime_format/types.js +103 -0
- package/dist/src/datetime_format/types.js.map +1 -0
- package/dist/src/datetime_format/validate.d.ts +51 -0
- package/dist/src/datetime_format/validate.d.ts.map +1 -0
- package/dist/src/datetime_format/validate.js +208 -0
- package/dist/src/datetime_format/validate.js.map +1 -0
- package/dist/src/default.d.ts +21 -0
- package/dist/src/default.d.ts.map +1 -0
- package/dist/src/default.js +82 -0
- package/dist/src/default.js.map +1 -0
- package/dist/src/eastir.d.ts +33 -0
- package/dist/src/eastir.d.ts.map +1 -0
- package/dist/src/eastir.js +92 -0
- package/dist/src/eastir.js.map +1 -0
- package/dist/src/error.d.ts +13 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +8 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/expr/array.d.ts +1711 -0
- package/dist/src/expr/array.d.ts.map +1 -0
- package/dist/src/expr/array.js +1805 -0
- package/dist/src/expr/array.js.map +1 -0
- package/dist/src/expr/ast.d.ts +17 -0
- package/dist/src/expr/ast.d.ts.map +1 -0
- package/dist/src/expr/ast.js +302 -0
- package/dist/src/expr/ast.js.map +1 -0
- package/dist/src/expr/blob.d.ts +141 -0
- package/dist/src/expr/blob.d.ts.map +1 -0
- package/dist/src/expr/blob.js +198 -0
- package/dist/src/expr/blob.js.map +1 -0
- package/dist/src/expr/block.d.ts +201 -0
- package/dist/src/expr/block.d.ts.map +1 -0
- package/dist/src/expr/block.js +1505 -0
- package/dist/src/expr/block.js.map +1 -0
- package/dist/src/expr/boolean.d.ts +207 -0
- package/dist/src/expr/boolean.d.ts.map +1 -0
- package/dist/src/expr/boolean.js +261 -0
- package/dist/src/expr/boolean.js.map +1 -0
- package/dist/src/expr/datetime.d.ts +544 -0
- package/dist/src/expr/datetime.d.ts.map +1 -0
- package/dist/src/expr/datetime.js +980 -0
- package/dist/src/expr/datetime.js.map +1 -0
- package/dist/src/expr/dict.d.ts +1242 -0
- package/dist/src/expr/dict.d.ts.map +1 -0
- package/dist/src/expr/dict.js +1492 -0
- package/dist/src/expr/dict.js.map +1 -0
- package/dist/src/expr/expr.d.ts +95 -0
- package/dist/src/expr/expr.d.ts.map +1 -0
- package/dist/src/expr/expr.js +171 -0
- package/dist/src/expr/expr.js.map +1 -0
- package/dist/src/expr/float.d.ts +357 -0
- package/dist/src/expr/float.d.ts.map +1 -0
- package/dist/src/expr/float.js +637 -0
- package/dist/src/expr/float.js.map +1 -0
- package/dist/src/expr/function.d.ts +46 -0
- package/dist/src/expr/function.d.ts.map +1 -0
- package/dist/src/expr/function.js +58 -0
- package/dist/src/expr/function.js.map +1 -0
- package/dist/src/expr/index.d.ts +450 -0
- package/dist/src/expr/index.d.ts.map +1 -0
- package/dist/src/expr/index.js +423 -0
- package/dist/src/expr/index.js.map +1 -0
- package/dist/src/expr/integer.d.ts +256 -0
- package/dist/src/expr/integer.d.ts.map +1 -0
- package/dist/src/expr/integer.js +311 -0
- package/dist/src/expr/integer.js.map +1 -0
- package/dist/src/expr/libs/array.d.ts +106 -0
- package/dist/src/expr/libs/array.d.ts.map +1 -0
- package/dist/src/expr/libs/array.js +140 -0
- package/dist/src/expr/libs/array.js.map +1 -0
- package/dist/src/expr/libs/blob.d.ts +42 -0
- package/dist/src/expr/libs/blob.d.ts.map +1 -0
- package/dist/src/expr/libs/blob.js +70 -0
- package/dist/src/expr/libs/blob.js.map +1 -0
- package/dist/src/expr/libs/datetime.d.ts +479 -0
- package/dist/src/expr/libs/datetime.d.ts.map +1 -0
- package/dist/src/expr/libs/datetime.js +624 -0
- package/dist/src/expr/libs/datetime.js.map +1 -0
- package/dist/src/expr/libs/dict.d.ts +66 -0
- package/dist/src/expr/libs/dict.d.ts.map +1 -0
- package/dist/src/expr/libs/dict.js +77 -0
- package/dist/src/expr/libs/dict.js.map +1 -0
- package/dist/src/expr/libs/float.d.ts +299 -0
- package/dist/src/expr/libs/float.d.ts.map +1 -0
- package/dist/src/expr/libs/float.js +564 -0
- package/dist/src/expr/libs/float.js.map +1 -0
- package/dist/src/expr/libs/integer.d.ts +228 -0
- package/dist/src/expr/libs/integer.d.ts.map +1 -0
- package/dist/src/expr/libs/integer.js +398 -0
- package/dist/src/expr/libs/integer.js.map +1 -0
- package/dist/src/expr/libs/set.d.ts +59 -0
- package/dist/src/expr/libs/set.d.ts.map +1 -0
- package/dist/src/expr/libs/set.js +69 -0
- package/dist/src/expr/libs/set.js.map +1 -0
- package/dist/src/expr/libs/string.d.ts +71 -0
- package/dist/src/expr/libs/string.d.ts.map +1 -0
- package/dist/src/expr/libs/string.js +75 -0
- package/dist/src/expr/libs/string.js.map +1 -0
- package/dist/src/expr/never.d.ts +15 -0
- package/dist/src/expr/never.d.ts.map +1 -0
- package/dist/src/expr/never.js +12 -0
- package/dist/src/expr/never.js.map +1 -0
- package/dist/src/expr/null.d.ts +15 -0
- package/dist/src/expr/null.d.ts.map +1 -0
- package/dist/src/expr/null.js +12 -0
- package/dist/src/expr/null.js.map +1 -0
- package/dist/src/expr/ref.d.ts +103 -0
- package/dist/src/expr/ref.d.ts.map +1 -0
- package/dist/src/expr/ref.js +131 -0
- package/dist/src/expr/ref.js.map +1 -0
- package/dist/src/expr/regex_validation.d.ts +25 -0
- package/dist/src/expr/regex_validation.d.ts.map +1 -0
- package/dist/src/expr/regex_validation.js +130 -0
- package/dist/src/expr/regex_validation.js.map +1 -0
- package/dist/src/expr/set.d.ts +1071 -0
- package/dist/src/expr/set.d.ts.map +1 -0
- package/dist/src/expr/set.js +1137 -0
- package/dist/src/expr/set.js.map +1 -0
- package/dist/src/expr/string.d.ts +414 -0
- package/dist/src/expr/string.d.ts.map +1 -0
- package/dist/src/expr/string.js +683 -0
- package/dist/src/expr/string.js.map +1 -0
- package/dist/src/expr/struct.d.ts +48 -0
- package/dist/src/expr/struct.d.ts.map +1 -0
- package/dist/src/expr/struct.js +65 -0
- package/dist/src/expr/struct.js.map +1 -0
- package/dist/src/expr/types.d.ts +68 -0
- package/dist/src/expr/types.d.ts.map +1 -0
- package/dist/src/expr/types.js +6 -0
- package/dist/src/expr/types.js.map +1 -0
- package/dist/src/expr/variant.d.ts +137 -0
- package/dist/src/expr/variant.d.ts.map +1 -0
- package/dist/src/expr/variant.js +105 -0
- package/dist/src/expr/variant.js.map +1 -0
- package/dist/src/fuzz.d.ts +80 -0
- package/dist/src/fuzz.d.ts.map +1 -0
- package/dist/src/fuzz.js +300 -0
- package/dist/src/fuzz.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal.d.ts +36 -0
- package/dist/src/internal.d.ts.map +1 -0
- package/dist/src/internal.js +11 -0
- package/dist/src/internal.js.map +1 -0
- package/dist/src/ir.d.ts +1571 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +56 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/location.d.ts +48 -0
- package/dist/src/location.d.ts.map +1 -0
- package/dist/src/location.js +62 -0
- package/dist/src/location.js.map +1 -0
- package/dist/src/platform.d.ts +21 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +8 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/serialization/beast.d.ts +39 -0
- package/dist/src/serialization/beast.d.ts.map +1 -0
- package/dist/src/serialization/beast.js +555 -0
- package/dist/src/serialization/beast.js.map +1 -0
- package/dist/src/serialization/beast2-stream.d.ts +38 -0
- package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
- package/dist/src/serialization/beast2-stream.js +665 -0
- package/dist/src/serialization/beast2-stream.js.map +1 -0
- package/dist/src/serialization/beast2.d.ts +41 -0
- package/dist/src/serialization/beast2.d.ts.map +1 -0
- package/dist/src/serialization/beast2.js +489 -0
- package/dist/src/serialization/beast2.js.map +1 -0
- package/dist/src/serialization/binary-utils.d.ts +151 -0
- package/dist/src/serialization/binary-utils.d.ts.map +1 -0
- package/dist/src/serialization/binary-utils.js +929 -0
- package/dist/src/serialization/binary-utils.js.map +1 -0
- package/dist/src/serialization/east.d.ts +84 -0
- package/dist/src/serialization/east.d.ts.map +1 -0
- package/dist/src/serialization/east.js +1802 -0
- package/dist/src/serialization/east.js.map +1 -0
- package/dist/src/serialization/index.d.ts +11 -0
- package/dist/src/serialization/index.d.ts.map +1 -0
- package/dist/src/serialization/index.js +12 -0
- package/dist/src/serialization/index.js.map +1 -0
- package/dist/src/serialization/json.d.ts +36 -0
- package/dist/src/serialization/json.d.ts.map +1 -0
- package/dist/src/serialization/json.js +849 -0
- package/dist/src/serialization/json.js.map +1 -0
- package/dist/src/type_of_type.d.ts +115 -0
- package/dist/src/type_of_type.d.ts.map +1 -0
- package/dist/src/type_of_type.js +362 -0
- package/dist/src/type_of_type.js.map +1 -0
- package/dist/src/types.d.ts +648 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1631 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// BufferWriter - Managed Uint8Array with auto-growth
|
|
7
|
+
// =============================================================================
|
|
8
|
+
export class BufferWriter {
|
|
9
|
+
buffer;
|
|
10
|
+
view;
|
|
11
|
+
offset = 0;
|
|
12
|
+
constructor(initialCapacity = 16384) {
|
|
13
|
+
this.buffer = new Uint8Array(initialCapacity);
|
|
14
|
+
this.view = new DataView(this.buffer.buffer);
|
|
15
|
+
}
|
|
16
|
+
ensureCapacity(needed) {
|
|
17
|
+
const required = this.offset + needed;
|
|
18
|
+
if (required <= this.buffer.length) {
|
|
19
|
+
return; // sufficient capacity
|
|
20
|
+
}
|
|
21
|
+
// Exponential growth: min 2x, max +1GB per resize
|
|
22
|
+
const doubled = this.buffer.length * 2;
|
|
23
|
+
const maxGrowth = this.buffer.length + 1024 * 1024 * 1024;
|
|
24
|
+
const newSize = Math.max(Math.min(doubled, maxGrowth), required);
|
|
25
|
+
const newBuffer = new Uint8Array(newSize);
|
|
26
|
+
newBuffer.set(this.buffer);
|
|
27
|
+
this.buffer = newBuffer;
|
|
28
|
+
this.view = new DataView(newBuffer.buffer);
|
|
29
|
+
}
|
|
30
|
+
writeUint8(value) {
|
|
31
|
+
this.ensureCapacity(1);
|
|
32
|
+
this.view.setUint8(this.offset, value);
|
|
33
|
+
this.offset += 1;
|
|
34
|
+
}
|
|
35
|
+
writeUint16LE(value) {
|
|
36
|
+
this.ensureCapacity(2);
|
|
37
|
+
this.view.setUint16(this.offset, value, true);
|
|
38
|
+
this.offset += 2;
|
|
39
|
+
}
|
|
40
|
+
writeUint16BE(value) {
|
|
41
|
+
this.ensureCapacity(2);
|
|
42
|
+
this.view.setUint16(this.offset, value, false);
|
|
43
|
+
this.offset += 2;
|
|
44
|
+
}
|
|
45
|
+
writeBytes(bytes) {
|
|
46
|
+
this.ensureCapacity(bytes.length);
|
|
47
|
+
this.buffer.set(bytes, this.offset);
|
|
48
|
+
this.offset += bytes.length;
|
|
49
|
+
}
|
|
50
|
+
// ===========================================================================
|
|
51
|
+
// Varint encoding (LEB128 - used by beast2)
|
|
52
|
+
// ===========================================================================
|
|
53
|
+
writeVarint(value) {
|
|
54
|
+
// Unsigned LEB128 encoding for sizes/lengths/tags
|
|
55
|
+
if (value < 0) {
|
|
56
|
+
throw new Error(`writeVarint requires non-negative value, got ${value}`);
|
|
57
|
+
}
|
|
58
|
+
if (!Number.isSafeInteger(value)) {
|
|
59
|
+
throw new Error(`writeVarint requires safe integer, got ${value}`);
|
|
60
|
+
}
|
|
61
|
+
this.ensureCapacity(10); // max 10 bytes for 64-bit value
|
|
62
|
+
// Use BigInt to avoid precision loss with large numbers
|
|
63
|
+
let n = BigInt(value);
|
|
64
|
+
while (n >= 0x80n) {
|
|
65
|
+
this.view.setUint8(this.offset, Number(n & 0x7fn) | 0x80);
|
|
66
|
+
this.offset += 1;
|
|
67
|
+
n >>= 7n;
|
|
68
|
+
}
|
|
69
|
+
this.view.setUint8(this.offset, Number(n));
|
|
70
|
+
this.offset += 1;
|
|
71
|
+
}
|
|
72
|
+
writeZigzag(value) {
|
|
73
|
+
// Zigzag encoding: (n << 1) ^ (n >> 63)
|
|
74
|
+
const zigzagged = (value << 1n) ^ (value >> 63n);
|
|
75
|
+
// Convert to unsigned LEB128
|
|
76
|
+
this.ensureCapacity(10); // max 10 bytes for 64-bit value
|
|
77
|
+
let n = zigzagged;
|
|
78
|
+
while (n >= 0x80n) {
|
|
79
|
+
this.view.setUint8(this.offset, Number(n & 0x7fn) | 0x80);
|
|
80
|
+
this.offset += 1;
|
|
81
|
+
n >>= 7n;
|
|
82
|
+
}
|
|
83
|
+
this.view.setUint8(this.offset, Number(n));
|
|
84
|
+
this.offset += 1;
|
|
85
|
+
}
|
|
86
|
+
// ===========================================================================
|
|
87
|
+
// Integer encoding - beast v1 (big-endian with sign-bit flip for ordering)
|
|
88
|
+
// ===========================================================================
|
|
89
|
+
writeInt64Twiddled(value) {
|
|
90
|
+
this.ensureCapacity(8);
|
|
91
|
+
// Flip sign bit for memcmp ordering (total ordering for all int64 values)
|
|
92
|
+
// Positive values become > 0x8000_0000_0000_0000
|
|
93
|
+
// Negative values become < 0x8000_0000_0000_0000
|
|
94
|
+
// Note: Using -9223372036854775808n (MIN_INT64 = -2^63) which is 0x8000_0000_0000_0000 as signed
|
|
95
|
+
const twiddled = value >= 0n
|
|
96
|
+
? value + -9223372036854775808n // Add MIN_INT64 to flip sign bit
|
|
97
|
+
: value - -9223372036854775808n; // Subtract MIN_INT64 (same as add MAX_INT64+1)
|
|
98
|
+
this.view.setBigInt64(this.offset, twiddled, false); // big-endian
|
|
99
|
+
this.offset += 8;
|
|
100
|
+
}
|
|
101
|
+
// ===========================================================================
|
|
102
|
+
// Float encoding - beast2 (little-endian, canonical NaN)
|
|
103
|
+
// ===========================================================================
|
|
104
|
+
writeFloat64LE(value) {
|
|
105
|
+
this.ensureCapacity(8);
|
|
106
|
+
// NaN normalization: always write canonical quiet NaN
|
|
107
|
+
// 0x7FF8000000000000 is the bit pattern (same for big and little-endian)
|
|
108
|
+
// The littleEndian flag controls byte order in memory
|
|
109
|
+
if (Number.isNaN(value)) {
|
|
110
|
+
this.view.setBigUint64(this.offset, 0x7ff8000000000000n, true); // little-endian byte order (Avro-compatible)
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.view.setFloat64(this.offset, value, true); // little-endian (Avro-compatible)
|
|
114
|
+
}
|
|
115
|
+
this.offset += 8;
|
|
116
|
+
}
|
|
117
|
+
// ===========================================================================
|
|
118
|
+
// Float encoding - beast v1 (big-endian with bit-twiddling for total ordering)
|
|
119
|
+
// ===========================================================================
|
|
120
|
+
// Preallocated buffers for float bit-twiddling (reused across calls)
|
|
121
|
+
static _floatBuffer = new Float64Array(1);
|
|
122
|
+
static _uintBuffer = new BigUint64Array(BufferWriter._floatBuffer.buffer);
|
|
123
|
+
writeFloat64Twiddled(value) {
|
|
124
|
+
this.ensureCapacity(8);
|
|
125
|
+
// Convert to bit pattern using preallocated buffers
|
|
126
|
+
BufferWriter._floatBuffer[0] = value;
|
|
127
|
+
let uint = BufferWriter._uintBuffer[0]; // Non-null assertion: array access always succeeds for typed arrays
|
|
128
|
+
// Bit-twiddling for total ordering (including NaNs, infinities, signed zeros)
|
|
129
|
+
// This ensures memcmp(encoded_a, encoded_b) matches float comparison
|
|
130
|
+
if (uint < 0x8000000000000000n) {
|
|
131
|
+
// Positive float (sign bit = 0) - flip sign bit
|
|
132
|
+
// Maps: 0.0 -> 0x8000..., +inf -> 0xFFF0..., NaN -> 0xFFF8...
|
|
133
|
+
uint = uint ^ 0x8000000000000000n;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Negative float (sign bit = 1) - flip all bits
|
|
137
|
+
// Maps: -0.0 -> 0x7FFF..., -inf -> 0x000F..., -smallest -> 0x7FFF...
|
|
138
|
+
uint = ~uint;
|
|
139
|
+
}
|
|
140
|
+
this.view.setBigUint64(this.offset, uint, false); // big-endian
|
|
141
|
+
this.offset += 8;
|
|
142
|
+
}
|
|
143
|
+
// ===========================================================================
|
|
144
|
+
// String encoding - beast2 (varint-length prefixed)
|
|
145
|
+
// ===========================================================================
|
|
146
|
+
writeStringUtf8Varint(str) {
|
|
147
|
+
// Encode UTF-8 first to know the length
|
|
148
|
+
const maxUtf8Length = str.length * 3; // worst case: 3 bytes per UTF-16 code unit
|
|
149
|
+
this.ensureCapacity(10 + maxUtf8Length); // varint length + string bytes
|
|
150
|
+
// Encode string into a temporary position (after max varint size)
|
|
151
|
+
const tempStringStart = this.offset + 10; // Reserve max varint bytes
|
|
152
|
+
const stringEnd = utf8EncodeInto(str, this.buffer, tempStringStart);
|
|
153
|
+
const utf8ByteLength = stringEnd - tempStringStart;
|
|
154
|
+
// Now write the actual varint length
|
|
155
|
+
this.writeVarint(utf8ByteLength);
|
|
156
|
+
const varintEnd = this.offset;
|
|
157
|
+
// Shift string data to immediately after the varint (if needed)
|
|
158
|
+
if (varintEnd !== tempStringStart) {
|
|
159
|
+
this.buffer.copyWithin(varintEnd, tempStringStart, stringEnd);
|
|
160
|
+
}
|
|
161
|
+
// Update offset to end of string
|
|
162
|
+
this.offset = varintEnd + utf8ByteLength;
|
|
163
|
+
}
|
|
164
|
+
// ===========================================================================
|
|
165
|
+
// String encoding - beast v1 (null-terminated)
|
|
166
|
+
// ===========================================================================
|
|
167
|
+
writeStringUtf8Null(str) {
|
|
168
|
+
const maxUtf8Length = str.length * 3; // worst case: 3 bytes per UTF-16 code unit
|
|
169
|
+
this.ensureCapacity(maxUtf8Length + 1); // +1 for null byte
|
|
170
|
+
this.offset = utf8EncodeInto(str, this.buffer, this.offset);
|
|
171
|
+
this.view.setUint8(this.offset, 0); // null terminator
|
|
172
|
+
this.offset += 1;
|
|
173
|
+
}
|
|
174
|
+
get size() {
|
|
175
|
+
return this.offset;
|
|
176
|
+
}
|
|
177
|
+
get currentOffset() {
|
|
178
|
+
return this.offset;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Extract the current buffer contents and reset for reuse.
|
|
182
|
+
* Used by streaming encoders to generate chunks.
|
|
183
|
+
*/
|
|
184
|
+
pop() {
|
|
185
|
+
const chunk = this.buffer.subarray(0, this.offset);
|
|
186
|
+
this.offset = 0;
|
|
187
|
+
return chunk;
|
|
188
|
+
}
|
|
189
|
+
toUint8Array() {
|
|
190
|
+
return this.buffer.subarray(0, this.offset);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// Stateless read functions
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// ===========================================================================
|
|
197
|
+
// Varint (beast2)
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
export function readVarint(buffer, offset) {
|
|
200
|
+
// Unsigned LEB128 → number for sizes/lengths/tags
|
|
201
|
+
// Use BigInt to avoid precision loss, then convert to number
|
|
202
|
+
let result = 0n;
|
|
203
|
+
let shift = 0n;
|
|
204
|
+
while (true) {
|
|
205
|
+
if (offset >= buffer.length) {
|
|
206
|
+
throw new Error(`Buffer underflow reading varint at offset ${offset}`);
|
|
207
|
+
}
|
|
208
|
+
const byte = buffer[offset++];
|
|
209
|
+
result |= BigInt(byte & 0x7F) << shift;
|
|
210
|
+
if ((byte & 0x80) === 0) {
|
|
211
|
+
break; // last byte
|
|
212
|
+
}
|
|
213
|
+
shift += 7n;
|
|
214
|
+
if (shift >= 64n) {
|
|
215
|
+
throw new Error(`Varint too long at offset ${offset - 1}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const numResult = Number(result);
|
|
219
|
+
if (!Number.isSafeInteger(numResult)) {
|
|
220
|
+
throw new Error(`Varint value ${result} exceeds MAX_SAFE_INTEGER`);
|
|
221
|
+
}
|
|
222
|
+
return [numResult, offset];
|
|
223
|
+
}
|
|
224
|
+
export function readZigzag(buffer, offset) {
|
|
225
|
+
// Zigzag varint → bigint for signed IntegerType values
|
|
226
|
+
let result = 0n;
|
|
227
|
+
let shift = 0n;
|
|
228
|
+
while (true) {
|
|
229
|
+
if (offset >= buffer.length) {
|
|
230
|
+
throw new Error(`Buffer underflow reading zigzag at offset ${offset}`);
|
|
231
|
+
}
|
|
232
|
+
const byte = buffer[offset++];
|
|
233
|
+
result |= BigInt(byte & 0x7F) << shift;
|
|
234
|
+
if ((byte & 0x80) === 0) {
|
|
235
|
+
break; // last byte
|
|
236
|
+
}
|
|
237
|
+
shift += 7n;
|
|
238
|
+
if (shift >= 64n) {
|
|
239
|
+
throw new Error(`Zigzag varint too long at offset ${offset - 1}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Decode zigzag: (n >> 1) ^ -(n & 1)
|
|
243
|
+
const decoded = (result >> 1n) ^ -(result & 1n);
|
|
244
|
+
return [decoded, offset];
|
|
245
|
+
}
|
|
246
|
+
// ===========================================================================
|
|
247
|
+
// Integer - beast v1 (big-endian twiddled)
|
|
248
|
+
// ===========================================================================
|
|
249
|
+
export function readInt64Twiddled(view, offset) {
|
|
250
|
+
if (offset + 8 > view.byteLength) {
|
|
251
|
+
throw new Error(`Buffer underflow reading int64 at offset ${offset}`);
|
|
252
|
+
}
|
|
253
|
+
const twiddled = view.getBigInt64(offset, false); // big-endian
|
|
254
|
+
// Undo sign-bit flip (same operation as encoding - XOR is self-inverse)
|
|
255
|
+
// Note: Using -9223372036854775808n (MIN_INT64 = -2^63)
|
|
256
|
+
const value = twiddled >= 0n
|
|
257
|
+
? twiddled + -9223372036854775808n
|
|
258
|
+
: twiddled - -9223372036854775808n;
|
|
259
|
+
return [value, offset + 8];
|
|
260
|
+
}
|
|
261
|
+
// ===========================================================================
|
|
262
|
+
// Float - beast2 (little-endian with NaN validation)
|
|
263
|
+
// ===========================================================================
|
|
264
|
+
export function readFloat64LE(view, offset) {
|
|
265
|
+
if (offset + 8 > view.byteLength) {
|
|
266
|
+
throw new Error(`Buffer underflow reading float64 at offset ${offset}`);
|
|
267
|
+
}
|
|
268
|
+
const bits = view.getBigUint64(offset, true); // little-endian byte order (Avro-compatible)
|
|
269
|
+
// Check for NaN (exponent = all 1s, mantissa != 0)
|
|
270
|
+
// Bit pattern is the same regardless of endianness
|
|
271
|
+
if ((bits & 0x7ff0000000000000n) === 0x7ff0000000000000n &&
|
|
272
|
+
(bits & 0x000fffffffffffffn) !== 0n) {
|
|
273
|
+
// It's a NaN - must be canonical quiet NaN (positive or negative)
|
|
274
|
+
if (bits !== 0x7ff8000000000000n && bits !== 0xfff8000000000000n) {
|
|
275
|
+
throw new Error(`Non-canonical NaN at offset ${offset}: 0x${bits.toString(16)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return view.getFloat64(offset, true); // little-endian byte order (Avro-compatible)
|
|
279
|
+
}
|
|
280
|
+
// ===========================================================================
|
|
281
|
+
// Float - beast v1 (big-endian twiddled)
|
|
282
|
+
// ===========================================================================
|
|
283
|
+
// Preallocated buffers for float bit-twiddling (reused across calls)
|
|
284
|
+
const _floatReadUintBuffer = new BigUint64Array(1);
|
|
285
|
+
const _floatReadBuffer = new Float64Array(_floatReadUintBuffer.buffer);
|
|
286
|
+
export function readFloat64Twiddled(view, offset) {
|
|
287
|
+
if (offset + 8 > view.byteLength) {
|
|
288
|
+
throw new Error(`Buffer underflow reading float64 at offset ${offset}`);
|
|
289
|
+
}
|
|
290
|
+
let uint = view.getBigUint64(offset, false); // big-endian
|
|
291
|
+
// Undo bit-twiddling for total ordering
|
|
292
|
+
if (uint >= 0x8000000000000000n) {
|
|
293
|
+
// Positive float - flip sign bit
|
|
294
|
+
uint = uint ^ 0x8000000000000000n;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// Negative float - flip all bits
|
|
298
|
+
uint = ~uint;
|
|
299
|
+
}
|
|
300
|
+
_floatReadUintBuffer[0] = uint;
|
|
301
|
+
return _floatReadBuffer[0]; // Non-null assertion: array access always succeeds for typed arrays
|
|
302
|
+
}
|
|
303
|
+
// ===========================================================================
|
|
304
|
+
// String - beast2 (varint-length)
|
|
305
|
+
// ===========================================================================
|
|
306
|
+
export function readStringUtf8Varint(buffer, offset) {
|
|
307
|
+
// Read varint length
|
|
308
|
+
const [length, newOffset] = readVarint(buffer, offset);
|
|
309
|
+
if (newOffset + length > buffer.length) {
|
|
310
|
+
throw new Error(`Buffer underflow reading string at offset ${offset}, length ${length}`);
|
|
311
|
+
}
|
|
312
|
+
// Decode UTF-8
|
|
313
|
+
const str = utf8Decode(buffer, newOffset, length);
|
|
314
|
+
return [str, newOffset + length];
|
|
315
|
+
}
|
|
316
|
+
// ===========================================================================
|
|
317
|
+
// String - beast v1 (null-terminated)
|
|
318
|
+
// ===========================================================================
|
|
319
|
+
export function readStringUtf8Null(buffer, offset) {
|
|
320
|
+
// Single-pass: decode UTF-8 and find null terminator simultaneously (like ELARACore)
|
|
321
|
+
const codePoints = [];
|
|
322
|
+
let i = offset;
|
|
323
|
+
while (i < buffer.length) {
|
|
324
|
+
const byte1 = buffer[i++];
|
|
325
|
+
if (byte1 === 0) {
|
|
326
|
+
// Null terminator found
|
|
327
|
+
return [String.fromCharCode.apply(String, codePoints), i];
|
|
328
|
+
}
|
|
329
|
+
if ((byte1 & 0x80) === 0) {
|
|
330
|
+
// 1-byte (ASCII)
|
|
331
|
+
codePoints.push(byte1);
|
|
332
|
+
}
|
|
333
|
+
else if ((byte1 & 0xE0) === 0xC0) {
|
|
334
|
+
// 2-byte
|
|
335
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
336
|
+
codePoints.push(((byte1 & 0x1F) << 6) | byte2);
|
|
337
|
+
}
|
|
338
|
+
else if ((byte1 & 0xF0) === 0xE0) {
|
|
339
|
+
// 3-byte
|
|
340
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
341
|
+
const byte3 = buffer[i++] & 0x3F;
|
|
342
|
+
codePoints.push(((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3);
|
|
343
|
+
}
|
|
344
|
+
else if ((byte1 & 0xF8) === 0xF0) {
|
|
345
|
+
// 4-byte
|
|
346
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
347
|
+
const byte3 = buffer[i++] & 0x3F;
|
|
348
|
+
const byte4 = buffer[i++] & 0x3F;
|
|
349
|
+
let codePoint = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
|
|
350
|
+
// Encode as surrogate pair if needed
|
|
351
|
+
if (codePoint > 0xFFFF) {
|
|
352
|
+
codePoint -= 0x10000;
|
|
353
|
+
codePoints.push(((codePoint >>> 10) & 0x3FF) | 0xD800);
|
|
354
|
+
codePoint = 0xDC00 | (codePoint & 0x3FF);
|
|
355
|
+
}
|
|
356
|
+
codePoints.push(codePoint);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Invalid UTF-8, skip byte
|
|
360
|
+
codePoints.push(byte1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
throw new Error(`No null terminator found for string at offset ${offset}`);
|
|
364
|
+
}
|
|
365
|
+
// =============================================================================
|
|
366
|
+
// UTF-8 encoding/decoding (shared by both formats)
|
|
367
|
+
// =============================================================================
|
|
368
|
+
// Adapted from old implementation, optimized for small strings
|
|
369
|
+
export function utf8EncodeInto(str, buffer, offset) {
|
|
370
|
+
let pos = 0;
|
|
371
|
+
const len = str.length;
|
|
372
|
+
let at = offset;
|
|
373
|
+
while (pos < len) {
|
|
374
|
+
let value = str.charCodeAt(pos++);
|
|
375
|
+
// Handle surrogate pairs
|
|
376
|
+
if (value >= 0xD800 && value <= 0xDBFF) {
|
|
377
|
+
// high surrogate
|
|
378
|
+
if (pos < len) {
|
|
379
|
+
const extra = str.charCodeAt(pos);
|
|
380
|
+
if ((extra & 0xFC00) === 0xDC00) {
|
|
381
|
+
++pos;
|
|
382
|
+
value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (value >= 0xD800 && value <= 0xDBFF) {
|
|
386
|
+
continue; // drop lone surrogate
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if ((value & 0xFFFFFF80) === 0) {
|
|
390
|
+
// 1-byte (ASCII)
|
|
391
|
+
buffer[at++] = value;
|
|
392
|
+
}
|
|
393
|
+
else if ((value & 0xFFFFF800) === 0) {
|
|
394
|
+
// 2-byte
|
|
395
|
+
buffer[at++] = ((value >>> 6) & 0x1F) | 0xC0;
|
|
396
|
+
buffer[at++] = (value & 0x3F) | 0x80;
|
|
397
|
+
}
|
|
398
|
+
else if ((value & 0xFFFF0000) === 0) {
|
|
399
|
+
// 3-byte
|
|
400
|
+
buffer[at++] = ((value >>> 12) & 0x0F) | 0xE0;
|
|
401
|
+
buffer[at++] = ((value >>> 6) & 0x3F) | 0x80;
|
|
402
|
+
buffer[at++] = (value & 0x3F) | 0x80;
|
|
403
|
+
}
|
|
404
|
+
else if ((value & 0xFFE00000) === 0) {
|
|
405
|
+
// 4-byte
|
|
406
|
+
buffer[at++] = ((value >>> 18) & 0x07) | 0xF0;
|
|
407
|
+
buffer[at++] = ((value >>> 12) & 0x3F) | 0x80;
|
|
408
|
+
buffer[at++] = ((value >>> 6) & 0x3F) | 0x80;
|
|
409
|
+
buffer[at++] = (value & 0x3F) | 0x80;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
continue; // out of range
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return at;
|
|
416
|
+
}
|
|
417
|
+
export function utf8Decode(buffer, offset, length) {
|
|
418
|
+
const end = offset + length;
|
|
419
|
+
const codePoints = [];
|
|
420
|
+
let i = offset;
|
|
421
|
+
while (i < end) {
|
|
422
|
+
const byte1 = buffer[i++];
|
|
423
|
+
if ((byte1 & 0x80) === 0) {
|
|
424
|
+
// 1-byte (ASCII)
|
|
425
|
+
codePoints.push(byte1);
|
|
426
|
+
}
|
|
427
|
+
else if ((byte1 & 0xE0) === 0xC0) {
|
|
428
|
+
// 2-byte
|
|
429
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
430
|
+
codePoints.push(((byte1 & 0x1F) << 6) | byte2);
|
|
431
|
+
}
|
|
432
|
+
else if ((byte1 & 0xF0) === 0xE0) {
|
|
433
|
+
// 3-byte
|
|
434
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
435
|
+
const byte3 = buffer[i++] & 0x3F;
|
|
436
|
+
codePoints.push(((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3);
|
|
437
|
+
}
|
|
438
|
+
else if ((byte1 & 0xF8) === 0xF0) {
|
|
439
|
+
// 4-byte
|
|
440
|
+
const byte2 = buffer[i++] & 0x3F;
|
|
441
|
+
const byte3 = buffer[i++] & 0x3F;
|
|
442
|
+
const byte4 = buffer[i++] & 0x3F;
|
|
443
|
+
let codePoint = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
|
|
444
|
+
// Encode as surrogate pair if needed
|
|
445
|
+
if (codePoint > 0xFFFF) {
|
|
446
|
+
codePoint -= 0x10000;
|
|
447
|
+
codePoints.push(((codePoint >>> 10) & 0x3FF) | 0xD800);
|
|
448
|
+
codePoint = 0xDC00 | (codePoint & 0x3FF);
|
|
449
|
+
}
|
|
450
|
+
codePoints.push(codePoint);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// Invalid UTF-8, skip byte
|
|
454
|
+
codePoints.push(byte1);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return String.fromCharCode(...codePoints);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* StreamBufferReader - Helper for reading bytes from a Web Streams ReadableStream
|
|
461
|
+
* with lookahead support for parsing.
|
|
462
|
+
*
|
|
463
|
+
* Maintains an internal buffer of unconsumed bytes pulled from the underlying stream.
|
|
464
|
+
*/
|
|
465
|
+
export class StreamBufferReader {
|
|
466
|
+
reader;
|
|
467
|
+
buffer;
|
|
468
|
+
bufferOffset;
|
|
469
|
+
done;
|
|
470
|
+
totalBytesConsumed;
|
|
471
|
+
// Preallocated buffers for efficient primitive decoding (reused across calls)
|
|
472
|
+
tempBuffer8 = new Uint8Array(8);
|
|
473
|
+
tempView8 = new DataView(this.tempBuffer8.buffer);
|
|
474
|
+
floatUintBuffer = new BigUint64Array(1);
|
|
475
|
+
floatBuffer = new Float64Array(this.floatUintBuffer.buffer);
|
|
476
|
+
constructor(stream) {
|
|
477
|
+
this.reader = stream.getReader();
|
|
478
|
+
this.buffer = new Uint8Array(0);
|
|
479
|
+
this.bufferOffset = 0;
|
|
480
|
+
this.done = false;
|
|
481
|
+
this.totalBytesConsumed = 0;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get the current position in the stream (total bytes read so far).
|
|
485
|
+
*/
|
|
486
|
+
get position() {
|
|
487
|
+
return this.totalBytesConsumed + this.bufferOffset;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Ensure we have at least `count` bytes available in the buffer.
|
|
491
|
+
* Pulls from underlying stream as needed.
|
|
492
|
+
*/
|
|
493
|
+
async ensureBytes(count) {
|
|
494
|
+
// Keep pulling chunks until we have enough bytes
|
|
495
|
+
while (true) {
|
|
496
|
+
const available = this.buffer.length - this.bufferOffset;
|
|
497
|
+
if (available >= count) {
|
|
498
|
+
return; // Already have enough
|
|
499
|
+
}
|
|
500
|
+
if (this.done) {
|
|
501
|
+
throw new Error(`Unexpected end of stream: needed ${count} bytes, only ${available} available`);
|
|
502
|
+
}
|
|
503
|
+
// Need more bytes - pull from stream
|
|
504
|
+
const { value, done } = await this.reader.read();
|
|
505
|
+
if (done) {
|
|
506
|
+
this.done = true;
|
|
507
|
+
if (this.buffer.length - this.bufferOffset < count) {
|
|
508
|
+
throw new Error(`Unexpected end of stream: needed ${count} bytes, only ${this.buffer.length - this.bufferOffset} available`);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (!value || value.length === 0) {
|
|
513
|
+
// Empty chunk, try again (loop will continue)
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
// Append new chunk to buffer
|
|
517
|
+
const unconsumed = this.buffer.subarray(this.bufferOffset);
|
|
518
|
+
this.totalBytesConsumed += this.bufferOffset;
|
|
519
|
+
const newBuffer = new Uint8Array(unconsumed.length + value.length);
|
|
520
|
+
newBuffer.set(unconsumed, 0);
|
|
521
|
+
newBuffer.set(value, unconsumed.length);
|
|
522
|
+
this.buffer = newBuffer;
|
|
523
|
+
this.bufferOffset = 0;
|
|
524
|
+
// Loop continues to check if we have enough bytes now
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Peek at the next byte without consuming it.
|
|
529
|
+
* Returns null if stream is done.
|
|
530
|
+
* Optimized to avoid promises unless necessary.
|
|
531
|
+
*/
|
|
532
|
+
peekByte() {
|
|
533
|
+
if (this.buffer.length - this.bufferOffset >= 1) {
|
|
534
|
+
return this.buffer[this.bufferOffset];
|
|
535
|
+
}
|
|
536
|
+
return this.peekByteAsync();
|
|
537
|
+
}
|
|
538
|
+
async peekByteAsync() {
|
|
539
|
+
try {
|
|
540
|
+
await this.ensureBytes(1);
|
|
541
|
+
return this.buffer[this.bufferOffset];
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return null; // End of stream
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Read and consume the next byte.
|
|
549
|
+
* Returns synchronously if data is buffered, otherwise returns a Promise.
|
|
550
|
+
* Throws if stream is done.
|
|
551
|
+
* Optimized to avoid promises unless necessary.
|
|
552
|
+
*/
|
|
553
|
+
readByte() {
|
|
554
|
+
// Fast path: if we have buffered data, return it synchronously
|
|
555
|
+
const available = this.buffer.length - this.bufferOffset;
|
|
556
|
+
if (available >= 1) {
|
|
557
|
+
const byte = this.buffer[this.bufferOffset];
|
|
558
|
+
this.bufferOffset++;
|
|
559
|
+
return byte;
|
|
560
|
+
}
|
|
561
|
+
// Slow path: need to fetch from stream
|
|
562
|
+
return this.readByteAsync();
|
|
563
|
+
}
|
|
564
|
+
async readByteAsync() {
|
|
565
|
+
await this.ensureBytes(1);
|
|
566
|
+
const byte = this.buffer[this.bufferOffset];
|
|
567
|
+
this.bufferOffset++;
|
|
568
|
+
return byte;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Read exactly `buffer.length` bytes into the provided buffer.
|
|
572
|
+
* Throws if not enough bytes available.
|
|
573
|
+
* Optimized to avoid promises unless necessary.
|
|
574
|
+
*/
|
|
575
|
+
readBytes(buffer) {
|
|
576
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
577
|
+
if (this.buffer.length - this.bufferOffset >= buffer.length) {
|
|
578
|
+
buffer.set(this.buffer.subarray(this.bufferOffset, this.bufferOffset + buffer.length));
|
|
579
|
+
this.bufferOffset += buffer.length;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
// Slow path: need to fetch from stream
|
|
583
|
+
return this.readBytesAsync(buffer);
|
|
584
|
+
}
|
|
585
|
+
async readBytesAsync(buffer) {
|
|
586
|
+
await this.ensureBytes(buffer.length);
|
|
587
|
+
buffer.set(this.buffer.subarray(this.bufferOffset, this.bufferOffset + buffer.length));
|
|
588
|
+
this.bufferOffset += buffer.length;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Read bytes until the terminator byte is found (exclusive).
|
|
592
|
+
* Returns the bytes read (not including terminator).
|
|
593
|
+
* The terminator byte is consumed.
|
|
594
|
+
* Optimized to avoid promises unless necessary.
|
|
595
|
+
*/
|
|
596
|
+
readUntilByte(terminator) {
|
|
597
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
598
|
+
for (let i = this.bufferOffset; i < this.buffer.length; i++) {
|
|
599
|
+
if (this.buffer[i] === terminator) {
|
|
600
|
+
const result = this.buffer.subarray(this.bufferOffset, i);
|
|
601
|
+
this.bufferOffset = i + 1; // consume terminator
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// Slow path: need to fetch from stream
|
|
606
|
+
return this.readUntilByteAsync(terminator);
|
|
607
|
+
}
|
|
608
|
+
async readUntilByteAsync(terminator) {
|
|
609
|
+
const result = [];
|
|
610
|
+
while (true) {
|
|
611
|
+
const byte = await this.readByte();
|
|
612
|
+
if (byte === terminator) {
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
result.push(byte);
|
|
616
|
+
}
|
|
617
|
+
return new Uint8Array(result);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Release the underlying reader.
|
|
621
|
+
* Should be called when done reading to release resources.
|
|
622
|
+
*/
|
|
623
|
+
async release() {
|
|
624
|
+
await this.reader.releaseLock();
|
|
625
|
+
}
|
|
626
|
+
// =============================================================================
|
|
627
|
+
// Specialized read methods for Beast v1 format
|
|
628
|
+
// =============================================================================
|
|
629
|
+
/**
|
|
630
|
+
* Read a twiddled big-endian int64 (Beast v1 format).
|
|
631
|
+
* Uses sign-bit flip for total ordering.
|
|
632
|
+
* Optimized to avoid promises unless necessary.
|
|
633
|
+
*/
|
|
634
|
+
readInt64Twiddled() {
|
|
635
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
636
|
+
if (this.buffer.length - this.bufferOffset >= 8) {
|
|
637
|
+
this.tempBuffer8.set(this.buffer.subarray(this.bufferOffset, this.bufferOffset + 8));
|
|
638
|
+
const twiddled = this.tempView8.getBigInt64(0, false); // big-endian
|
|
639
|
+
this.bufferOffset += 8;
|
|
640
|
+
// Undo sign-bit flip (same operation as encoding - XOR is self-inverse)
|
|
641
|
+
const value = twiddled >= 0n
|
|
642
|
+
? twiddled + -9223372036854775808n
|
|
643
|
+
: twiddled - -9223372036854775808n;
|
|
644
|
+
return value;
|
|
645
|
+
}
|
|
646
|
+
// Slow path: need to fetch from stream
|
|
647
|
+
return this.readInt64TwiddledAsync();
|
|
648
|
+
}
|
|
649
|
+
async readInt64TwiddledAsync() {
|
|
650
|
+
await this.readBytes(this.tempBuffer8);
|
|
651
|
+
const twiddled = this.tempView8.getBigInt64(0, false); // big-endian
|
|
652
|
+
// Undo sign-bit flip (same operation as encoding - XOR is self-inverse)
|
|
653
|
+
const value = twiddled >= 0n
|
|
654
|
+
? twiddled + -9223372036854775808n
|
|
655
|
+
: twiddled - -9223372036854775808n;
|
|
656
|
+
return value;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Read a twiddled big-endian float64 (Beast v1 format).
|
|
660
|
+
* Uses bit-twiddling for total ordering.
|
|
661
|
+
*/
|
|
662
|
+
readFloat64Twiddled() {
|
|
663
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
664
|
+
if (this.buffer.length - this.bufferOffset >= 8) {
|
|
665
|
+
this.tempBuffer8.set(this.buffer.subarray(this.bufferOffset, this.bufferOffset + 8));
|
|
666
|
+
this.bufferOffset += 8;
|
|
667
|
+
let uint = this.tempView8.getBigUint64(0, false); // big-endian
|
|
668
|
+
// Undo bit-twiddling for total ordering
|
|
669
|
+
if (uint >= 0x8000000000000000n) {
|
|
670
|
+
// Positive float - flip sign bit
|
|
671
|
+
uint = uint ^ 0x8000000000000000n;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Negative float - flip all bits
|
|
675
|
+
uint = ~uint;
|
|
676
|
+
}
|
|
677
|
+
// Convert to float using preallocated buffers
|
|
678
|
+
this.floatUintBuffer[0] = uint;
|
|
679
|
+
return this.floatBuffer[0];
|
|
680
|
+
}
|
|
681
|
+
// Slow path: need to fetch from stream
|
|
682
|
+
return this.readFloat64TwiddledAsync();
|
|
683
|
+
}
|
|
684
|
+
async readFloat64TwiddledAsync() {
|
|
685
|
+
await this.readBytes(this.tempBuffer8);
|
|
686
|
+
let uint = this.tempView8.getBigUint64(0, false); // big-endian
|
|
687
|
+
// Undo bit-twiddling for total ordering
|
|
688
|
+
if (uint >= 0x8000000000000000n) {
|
|
689
|
+
// Positive float - flip sign bit
|
|
690
|
+
uint = uint ^ 0x8000000000000000n;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
// Negative float - flip all bits
|
|
694
|
+
uint = ~uint;
|
|
695
|
+
}
|
|
696
|
+
// Convert to float using preallocated buffers
|
|
697
|
+
this.floatUintBuffer[0] = uint;
|
|
698
|
+
return this.floatBuffer[0];
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Read a null-terminated UTF-8 string (Beast v1 format).
|
|
702
|
+
* Decodes UTF-8 while searching for null terminator in single pass.
|
|
703
|
+
* Optimized to avoid promises unless necessary.
|
|
704
|
+
*/
|
|
705
|
+
readStringUtf8Null() {
|
|
706
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
707
|
+
for (let i = this.bufferOffset; i < this.buffer.length; i++) {
|
|
708
|
+
if (this.buffer[i] === 0) {
|
|
709
|
+
// Found null terminator in buffer
|
|
710
|
+
const str = utf8Decode(this.buffer, this.bufferOffset, i - this.bufferOffset);
|
|
711
|
+
this.bufferOffset = i + 1; // consume null terminator
|
|
712
|
+
return str;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Slow path: need to fetch from stream
|
|
716
|
+
return this.readStringUtf8NullAsync();
|
|
717
|
+
}
|
|
718
|
+
async readStringUtf8NullAsync() {
|
|
719
|
+
const codePoints = [];
|
|
720
|
+
while (true) {
|
|
721
|
+
const byte1 = await this.readByte();
|
|
722
|
+
if (byte1 === 0) {
|
|
723
|
+
// Null terminator found
|
|
724
|
+
return String.fromCharCode.apply(String, codePoints);
|
|
725
|
+
}
|
|
726
|
+
if ((byte1 & 0x80) === 0) {
|
|
727
|
+
// 1-byte (ASCII)
|
|
728
|
+
codePoints.push(byte1);
|
|
729
|
+
}
|
|
730
|
+
else if ((byte1 & 0xE0) === 0xC0) {
|
|
731
|
+
// 2-byte
|
|
732
|
+
const byte2 = (await this.readByte()) & 0x3F;
|
|
733
|
+
codePoints.push(((byte1 & 0x1F) << 6) | byte2);
|
|
734
|
+
}
|
|
735
|
+
else if ((byte1 & 0xF0) === 0xE0) {
|
|
736
|
+
// 3-byte
|
|
737
|
+
const byte2 = (await this.readByte()) & 0x3F;
|
|
738
|
+
const byte3 = (await this.readByte()) & 0x3F;
|
|
739
|
+
codePoints.push(((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3);
|
|
740
|
+
}
|
|
741
|
+
else if ((byte1 & 0xF8) === 0xF0) {
|
|
742
|
+
// 4-byte
|
|
743
|
+
const byte2 = (await this.readByte()) & 0x3F;
|
|
744
|
+
const byte3 = (await this.readByte()) & 0x3F;
|
|
745
|
+
const byte4 = (await this.readByte()) & 0x3F;
|
|
746
|
+
let codePoint = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
|
|
747
|
+
// Encode as surrogate pair if needed
|
|
748
|
+
if (codePoint > 0xFFFF) {
|
|
749
|
+
codePoint -= 0x10000;
|
|
750
|
+
codePoints.push(((codePoint >>> 10) & 0x3FF) | 0xD800);
|
|
751
|
+
codePoint = 0xDC00 | (codePoint & 0x3FF);
|
|
752
|
+
}
|
|
753
|
+
codePoints.push(codePoint);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
// Invalid UTF-8, skip byte
|
|
757
|
+
codePoints.push(byte1);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// =============================================================================
|
|
762
|
+
// Specialized read methods for Beast v2 format
|
|
763
|
+
// =============================================================================
|
|
764
|
+
/**
|
|
765
|
+
* Read an unsigned varint (LEB128) - Beast v2 format.
|
|
766
|
+
* Returns a number (safe for lengths/tags up to 2^53-1).
|
|
767
|
+
* Optimized to avoid promises unless necessary.
|
|
768
|
+
*/
|
|
769
|
+
readVarint() {
|
|
770
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
771
|
+
if (this.buffer.length - this.bufferOffset >= 10) {
|
|
772
|
+
const [value, newOffset] = readVarint(this.buffer, this.bufferOffset);
|
|
773
|
+
this.bufferOffset = newOffset;
|
|
774
|
+
return value;
|
|
775
|
+
}
|
|
776
|
+
// Slow path: need to fetch from stream
|
|
777
|
+
return this.readVarintAsync();
|
|
778
|
+
}
|
|
779
|
+
async readVarintAsync() {
|
|
780
|
+
let result = 0;
|
|
781
|
+
let shift = 0;
|
|
782
|
+
while (true) {
|
|
783
|
+
const byte = await this.readByte();
|
|
784
|
+
// Use number operations for values that fit in safe integer range
|
|
785
|
+
if (shift < 49) { // 7 * 7 = 49 bits safely fits in number
|
|
786
|
+
result |= (byte & 0x7F) << shift;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
// Overflow to BigInt path for large values (rare)
|
|
790
|
+
let bigResult = BigInt(result);
|
|
791
|
+
bigResult |= BigInt(byte & 0x7F) << BigInt(shift);
|
|
792
|
+
if ((byte & 0x80) === 0) {
|
|
793
|
+
return Number(bigResult);
|
|
794
|
+
}
|
|
795
|
+
shift += 7;
|
|
796
|
+
while (true) {
|
|
797
|
+
const nextByte = await this.readByte();
|
|
798
|
+
shift += 7;
|
|
799
|
+
if (shift >= 64) {
|
|
800
|
+
throw new Error(`Varint too long`);
|
|
801
|
+
}
|
|
802
|
+
bigResult |= BigInt(nextByte & 0x7F) << BigInt(shift - 7);
|
|
803
|
+
if ((nextByte & 0x80) === 0) {
|
|
804
|
+
return Number(bigResult);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if ((byte & 0x80) === 0) {
|
|
809
|
+
break; // last byte
|
|
810
|
+
}
|
|
811
|
+
shift += 7;
|
|
812
|
+
}
|
|
813
|
+
return result;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Read a zigzag-encoded signed integer (varint) - Beast v2 format.
|
|
817
|
+
* Returns a bigint.
|
|
818
|
+
* Optimized to avoid promises unless necessary.
|
|
819
|
+
*/
|
|
820
|
+
readZigzag() {
|
|
821
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
822
|
+
if (this.buffer.length - this.bufferOffset >= 10) {
|
|
823
|
+
const [value, newOffset] = readZigzag(this.buffer, this.bufferOffset);
|
|
824
|
+
this.bufferOffset = newOffset;
|
|
825
|
+
return value;
|
|
826
|
+
}
|
|
827
|
+
// Slow path: need to fetch from stream
|
|
828
|
+
return this.readZigzagAsync();
|
|
829
|
+
}
|
|
830
|
+
async readZigzagAsync() {
|
|
831
|
+
let result = 0n;
|
|
832
|
+
let shift = 0n;
|
|
833
|
+
while (true) {
|
|
834
|
+
const byte = await this.readByte();
|
|
835
|
+
result |= BigInt(byte & 0x7F) << shift;
|
|
836
|
+
if ((byte & 0x80) === 0) {
|
|
837
|
+
break; // last byte
|
|
838
|
+
}
|
|
839
|
+
shift += 7n;
|
|
840
|
+
if (shift >= 64n) {
|
|
841
|
+
throw new Error(`Zigzag varint too long`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// Decode zigzag: (n >> 1) ^ -(n & 1)
|
|
845
|
+
const decoded = (result >> 1n) ^ -(result & 1n);
|
|
846
|
+
return decoded;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Read a little-endian float64 - Beast v2 format.
|
|
850
|
+
* Validates NaN values are canonical.
|
|
851
|
+
* Optimized to avoid promises unless necessary.
|
|
852
|
+
*/
|
|
853
|
+
readFloat64LE() {
|
|
854
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
855
|
+
if (this.buffer.length - this.bufferOffset >= 8) {
|
|
856
|
+
this.tempBuffer8.set(this.buffer.subarray(this.bufferOffset, this.bufferOffset + 8));
|
|
857
|
+
this.bufferOffset += 8;
|
|
858
|
+
const bits = this.tempView8.getBigUint64(0, true); // little-endian
|
|
859
|
+
// Check for NaN (exponent = all 1s, mantissa != 0)
|
|
860
|
+
if ((bits & 0x7ff0000000000000n) === 0x7ff0000000000000n &&
|
|
861
|
+
(bits & 0x000fffffffffffffn) !== 0n) {
|
|
862
|
+
// It's a NaN - must be canonical quiet NaN (positive or negative)
|
|
863
|
+
if (bits !== 0x7ff8000000000000n && bits !== 0xfff8000000000000n) {
|
|
864
|
+
throw new Error(`Non-canonical NaN`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return this.tempView8.getFloat64(0, true); // little-endian
|
|
868
|
+
}
|
|
869
|
+
// Slow path: need to fetch from stream
|
|
870
|
+
return this.readFloat64LEAsync();
|
|
871
|
+
}
|
|
872
|
+
async readFloat64LEAsync() {
|
|
873
|
+
await this.readBytes(this.tempBuffer8);
|
|
874
|
+
const bits = this.tempView8.getBigUint64(0, true); // little-endian
|
|
875
|
+
// Check for NaN (exponent = all 1s, mantissa != 0)
|
|
876
|
+
if ((bits & 0x7ff0000000000000n) === 0x7ff0000000000000n &&
|
|
877
|
+
(bits & 0x000fffffffffffffn) !== 0n) {
|
|
878
|
+
// It's a NaN - must be canonical quiet NaN (positive or negative)
|
|
879
|
+
if (bits !== 0x7ff8000000000000n && bits !== 0xfff8000000000000n) {
|
|
880
|
+
throw new Error(`Non-canonical NaN`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return this.tempView8.getFloat64(0, true); // little-endian
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Read a varint-length-prefixed UTF-8 string - Beast v2 format.
|
|
887
|
+
*/
|
|
888
|
+
readStringUtf8Varint() {
|
|
889
|
+
if (this.buffer.length - this.bufferOffset >= 10) {
|
|
890
|
+
// Fast path: if we have enough buffered data, read synchronously
|
|
891
|
+
const [length, newOffset] = readVarint(this.buffer, this.bufferOffset);
|
|
892
|
+
this.bufferOffset = newOffset;
|
|
893
|
+
if (this.buffer.length - this.bufferOffset >= length) {
|
|
894
|
+
const str = utf8Decode(this.buffer, this.bufferOffset, length);
|
|
895
|
+
this.bufferOffset += length;
|
|
896
|
+
return str;
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
return this.readStringUtf8SizedAsync(length);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Slow path: need to fetch from stream
|
|
903
|
+
return this.readStringUtf8VarintAsync();
|
|
904
|
+
}
|
|
905
|
+
async readStringUtf8VarintAsync() {
|
|
906
|
+
const length = await this.readVarint();
|
|
907
|
+
if (length === 0) {
|
|
908
|
+
return "";
|
|
909
|
+
}
|
|
910
|
+
// Read the string bytes
|
|
911
|
+
const bytes = new Uint8Array(length);
|
|
912
|
+
await this.readBytes(bytes);
|
|
913
|
+
// Decode UTF-8
|
|
914
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
915
|
+
return decoder.decode(bytes);
|
|
916
|
+
}
|
|
917
|
+
async readStringUtf8SizedAsync(length) {
|
|
918
|
+
if (length === 0) {
|
|
919
|
+
return "";
|
|
920
|
+
}
|
|
921
|
+
// Read the string bytes
|
|
922
|
+
const bytes = new Uint8Array(length);
|
|
923
|
+
await this.readBytes(bytes);
|
|
924
|
+
// Decode UTF-8
|
|
925
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
926
|
+
return decoder.decode(bytes);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
//# sourceMappingURL=binary-utils.js.map
|