@gjsify/v8 0.1.15 → 0.3.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/lib/esm/heap.js +37 -0
- package/lib/esm/index.js +79 -29
- package/lib/esm/serdes.js +536 -0
- package/lib/types/heap.d.ts +16 -0
- package/lib/types/index.d.ts +46 -7
- package/lib/types/serdes.d.ts +42 -0
- package/package.json +5 -5
- package/src/heap.ts +53 -0
- package/src/index.spec.ts +256 -14
- package/src/index.ts +88 -30
- package/src/serdes.ts +609 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/serdes.ts
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
// Reference: Node.js lib/v8.js (Serializer/Deserializer/DefaultSerializer/DefaultDeserializer)
|
|
2
|
+
// Reimplemented for GJS — V8 wire format v15 in pure TypeScript
|
|
3
|
+
|
|
4
|
+
import { Buffer } from 'node:buffer';
|
|
5
|
+
|
|
6
|
+
const WIRE_VERSION = 15;
|
|
7
|
+
|
|
8
|
+
// Tag bytes matching V8 ValueSerializer::Tag enum
|
|
9
|
+
const kVersion = 0xFF;
|
|
10
|
+
const kUndefined = 0x5F; // '_'
|
|
11
|
+
const kNull = 0x30; // '0'
|
|
12
|
+
const kTrue = 0x54; // 'T'
|
|
13
|
+
const kFalse = 0x46; // 'F'
|
|
14
|
+
const kInt32 = 0x49; // 'I' — ZigZag varint
|
|
15
|
+
const kUint32 = 0x55; // 'U' — varint
|
|
16
|
+
const kDouble = 0x4E; // 'N' — 8 bytes LE
|
|
17
|
+
const kBigInt = 0x5A; // 'Z'
|
|
18
|
+
const kOneByteString = 0x22; // '"' — Latin1, varint len + bytes
|
|
19
|
+
const kUtf8String = 0x63; // 'c' — UTF-8, varint len + bytes
|
|
20
|
+
const kDate = 0x44; // 'D' — float64 ms
|
|
21
|
+
const kRegExp = 0x52; // 'R'
|
|
22
|
+
const kBeginJSObject = 0x6F; // 'o'
|
|
23
|
+
const kEndJSObject = 0x7B; // '{' + varint property count
|
|
24
|
+
const kBeginDenseArray = 0x41; // 'A'
|
|
25
|
+
const kEndDenseArray = 0x24; // '$' + varint spare + varint length
|
|
26
|
+
const kArrayBuffer = 0x42; // 'B'
|
|
27
|
+
const kObjectReference = 0x5E; // '^' — backref, varint index
|
|
28
|
+
const kHostObject = 0x5C; // '\' — host object (TypedArray/DataView/Buffer)
|
|
29
|
+
|
|
30
|
+
// ─── varint helpers ───────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function writeVarint(buf: number[], n: number): void {
|
|
33
|
+
n = n >>> 0;
|
|
34
|
+
while (n >= 0x80) {
|
|
35
|
+
buf.push((n & 0x7F) | 0x80);
|
|
36
|
+
n >>>= 7;
|
|
37
|
+
}
|
|
38
|
+
buf.push(n);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readVarint(buf: Buffer, pos: number): [number, number] {
|
|
42
|
+
let result = 0, shift = 0, byte: number;
|
|
43
|
+
do {
|
|
44
|
+
if (pos >= buf.length) throw new Error('Unexpected end of buffer reading varint');
|
|
45
|
+
byte = buf[pos++];
|
|
46
|
+
result |= (byte & 0x7F) << shift;
|
|
47
|
+
shift += 7;
|
|
48
|
+
} while (byte & 0x80);
|
|
49
|
+
return [result >>> 0, pos];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function zigZagEncode(n: number): number {
|
|
53
|
+
return n >= 0 ? n * 2 : -n * 2 - 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function zigZagDecode(n: number): number {
|
|
57
|
+
return (n & 1) ? -(n >>> 1) - 1 : n >>> 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeFloat64(buf: number[], d: number): void {
|
|
61
|
+
const tmp = new DataView(new ArrayBuffer(8));
|
|
62
|
+
tmp.setFloat64(0, d, true /* LE */);
|
|
63
|
+
for (let i = 0; i < 8; i++) buf.push(tmp.getUint8(i));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readFloat64(buf: Buffer, pos: number): [number, number] {
|
|
67
|
+
if (pos + 8 > buf.length) throw new Error('ReadDouble() failed');
|
|
68
|
+
const view = new DataView(buf.buffer, buf.byteOffset + pos, 8);
|
|
69
|
+
return [view.getFloat64(0, true), pos + 8];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isOneByte(s: string): boolean {
|
|
73
|
+
for (let i = 0; i < s.length; i++) {
|
|
74
|
+
if (s.charCodeAt(i) > 0xFF) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── TypedArray type index (matching Node.js DefaultSerializer) ───────────────
|
|
80
|
+
|
|
81
|
+
function typedArrayToIndex(abView: NodeJS.ArrayBufferView): number {
|
|
82
|
+
const tag = Object.prototype.toString.call(abView);
|
|
83
|
+
if (tag === '[object Int8Array]') return 0;
|
|
84
|
+
if (tag === '[object Uint8Array]') return 1;
|
|
85
|
+
if (tag === '[object Uint8ClampedArray]') return 2;
|
|
86
|
+
if (tag === '[object Int16Array]') return 3;
|
|
87
|
+
if (tag === '[object Uint16Array]') return 4;
|
|
88
|
+
if (tag === '[object Int32Array]') return 5;
|
|
89
|
+
if (tag === '[object Uint32Array]') return 6;
|
|
90
|
+
if (tag === '[object Float32Array]') return 7;
|
|
91
|
+
if (tag === '[object Float64Array]') return 8;
|
|
92
|
+
if (tag === '[object DataView]') return 9;
|
|
93
|
+
// index 10 = Buffer (FastBuffer)
|
|
94
|
+
if (tag === '[object BigInt64Array]') return 11;
|
|
95
|
+
if (tag === '[object BigUint64Array]') return 12;
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type TypedArrayCtor = new (buffer: ArrayBuffer, byteOffset: number, length: number) => NodeJS.ArrayBufferView;
|
|
100
|
+
|
|
101
|
+
function indexToTypedArray(index: number): TypedArrayCtor | undefined {
|
|
102
|
+
switch (index) {
|
|
103
|
+
case 0: return Int8Array as unknown as TypedArrayCtor;
|
|
104
|
+
case 1: return Uint8Array as unknown as TypedArrayCtor;
|
|
105
|
+
case 2: return Uint8ClampedArray as unknown as TypedArrayCtor;
|
|
106
|
+
case 3: return Int16Array as unknown as TypedArrayCtor;
|
|
107
|
+
case 4: return Uint16Array as unknown as TypedArrayCtor;
|
|
108
|
+
case 5: return Int32Array as unknown as TypedArrayCtor;
|
|
109
|
+
case 6: return Uint32Array as unknown as TypedArrayCtor;
|
|
110
|
+
case 7: return Float32Array as unknown as TypedArrayCtor;
|
|
111
|
+
case 8: return Float64Array as unknown as TypedArrayCtor;
|
|
112
|
+
case 9: return DataView as unknown as TypedArrayCtor;
|
|
113
|
+
case 10: return Buffer as unknown as TypedArrayCtor;
|
|
114
|
+
case 11: return BigInt64Array as unknown as TypedArrayCtor;
|
|
115
|
+
case 12: return BigUint64Array as unknown as TypedArrayCtor;
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Serializer ───────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export class Serializer {
|
|
123
|
+
protected _bytes: number[] = [];
|
|
124
|
+
protected _seen: object[] = [];
|
|
125
|
+
protected _treatAbvAsHostObjects = false;
|
|
126
|
+
_getDataCloneError: ErrorConstructor = Error;
|
|
127
|
+
|
|
128
|
+
writeHeader(): void {
|
|
129
|
+
this._bytes.push(kVersion);
|
|
130
|
+
writeVarint(this._bytes, WIRE_VERSION);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
writeValue(value: unknown): void {
|
|
134
|
+
// Check backref for objects/arrays/TypedArrays
|
|
135
|
+
if (value !== null && value !== undefined && typeof value === 'object') {
|
|
136
|
+
const idx = this._seen.indexOf(value as object);
|
|
137
|
+
if (idx !== -1) {
|
|
138
|
+
this._bytes.push(kObjectReference);
|
|
139
|
+
writeVarint(this._bytes, idx);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (value === undefined) {
|
|
145
|
+
this._bytes.push(kUndefined);
|
|
146
|
+
} else if (value === null) {
|
|
147
|
+
this._bytes.push(kNull);
|
|
148
|
+
} else if (value === true) {
|
|
149
|
+
this._bytes.push(kTrue);
|
|
150
|
+
} else if (value === false) {
|
|
151
|
+
this._bytes.push(kFalse);
|
|
152
|
+
} else if (typeof value === 'number') {
|
|
153
|
+
if (Number.isInteger(value) && value >= -(2 ** 31) && value <= 2 ** 32 - 1) {
|
|
154
|
+
if (value >= 0) {
|
|
155
|
+
this._bytes.push(kUint32);
|
|
156
|
+
writeVarint(this._bytes, value);
|
|
157
|
+
} else {
|
|
158
|
+
this._bytes.push(kInt32);
|
|
159
|
+
writeVarint(this._bytes, zigZagEncode(value));
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
this._bytes.push(kDouble);
|
|
163
|
+
writeFloat64(this._bytes, value);
|
|
164
|
+
}
|
|
165
|
+
} else if (typeof value === 'bigint') {
|
|
166
|
+
this._writeBigInt(value);
|
|
167
|
+
} else if (typeof value === 'string') {
|
|
168
|
+
this._writeString(value);
|
|
169
|
+
} else if (typeof value === 'object') {
|
|
170
|
+
const obj = value as object;
|
|
171
|
+
|
|
172
|
+
if (value instanceof Date) {
|
|
173
|
+
this._seen.push(obj);
|
|
174
|
+
this._bytes.push(kDate);
|
|
175
|
+
writeFloat64(this._bytes, (value as Date).getTime());
|
|
176
|
+
|
|
177
|
+
} else if (value instanceof RegExp) {
|
|
178
|
+
this._seen.push(obj);
|
|
179
|
+
this._bytes.push(kRegExp);
|
|
180
|
+
this._writeString((value as RegExp).source);
|
|
181
|
+
writeVarint(this._bytes, regExpFlagsToInt(value as RegExp));
|
|
182
|
+
|
|
183
|
+
} else if (value instanceof ArrayBuffer) {
|
|
184
|
+
this._seen.push(obj);
|
|
185
|
+
const bytes = new Uint8Array(value as ArrayBuffer);
|
|
186
|
+
this._bytes.push(kArrayBuffer);
|
|
187
|
+
writeVarint(this._bytes, bytes.length);
|
|
188
|
+
for (let i = 0; i < bytes.length; i++) this._bytes.push(bytes[i]);
|
|
189
|
+
|
|
190
|
+
} else if (this._treatAbvAsHostObjects &&
|
|
191
|
+
(ArrayBuffer.isView(value) || Buffer.isBuffer(value))) {
|
|
192
|
+
this._seen.push(obj);
|
|
193
|
+
this._bytes.push(kHostObject);
|
|
194
|
+
this._writeHostObject(obj as NodeJS.ArrayBufferView);
|
|
195
|
+
|
|
196
|
+
} else if (Array.isArray(value)) {
|
|
197
|
+
this._seen.push(obj);
|
|
198
|
+
const arr = value as unknown[];
|
|
199
|
+
this._bytes.push(kBeginDenseArray);
|
|
200
|
+
writeVarint(this._bytes, arr.length);
|
|
201
|
+
for (let i = 0; i < arr.length; i++) this.writeValue(arr[i]);
|
|
202
|
+
this._bytes.push(kEndDenseArray);
|
|
203
|
+
writeVarint(this._bytes, 0); // spare properties count
|
|
204
|
+
writeVarint(this._bytes, arr.length);
|
|
205
|
+
|
|
206
|
+
} else {
|
|
207
|
+
this._seen.push(obj);
|
|
208
|
+
this._bytes.push(kBeginJSObject);
|
|
209
|
+
const keys = Object.keys(obj);
|
|
210
|
+
for (const key of keys) {
|
|
211
|
+
this._writeString(key);
|
|
212
|
+
this.writeValue((obj as Record<string, unknown>)[key]);
|
|
213
|
+
}
|
|
214
|
+
this._bytes.push(kEndJSObject);
|
|
215
|
+
writeVarint(this._bytes, keys.length);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
throw new (this._getDataCloneError)(
|
|
219
|
+
`${String(value)} could not be cloned.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
releaseBuffer(): Buffer {
|
|
225
|
+
const result = Buffer.from(this._bytes);
|
|
226
|
+
this._bytes = [];
|
|
227
|
+
this._seen = [];
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
writeUint32(n: number): void {
|
|
232
|
+
writeVarint(this._bytes, n >>> 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
writeUint64(hi: number, lo: number): void {
|
|
236
|
+
writeVarint(this._bytes, hi >>> 0);
|
|
237
|
+
writeVarint(this._bytes, lo >>> 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
writeDouble(d: number): void {
|
|
241
|
+
writeFloat64(this._bytes, d);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
writeRawBytes(source: NodeJS.ArrayBufferView): void {
|
|
245
|
+
if (!ArrayBuffer.isView(source)) {
|
|
246
|
+
throw new TypeError('source must be a TypedArray or a DataView');
|
|
247
|
+
}
|
|
248
|
+
const bytes = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
249
|
+
for (let i = 0; i < bytes.length; i++) this._bytes.push(bytes[i]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_writeHostObject(_obj: object): void {
|
|
253
|
+
throw new (this._getDataCloneError)(
|
|
254
|
+
`Unserializable host object: ${Object.prototype.toString.call(_obj)}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
_setTreatArrayBufferViewsAsHostObjects(flag: boolean): void {
|
|
259
|
+
this._treatAbvAsHostObjects = flag;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private _writeString(s: string): void {
|
|
263
|
+
if (isOneByte(s)) {
|
|
264
|
+
this._bytes.push(kOneByteString);
|
|
265
|
+
writeVarint(this._bytes, s.length);
|
|
266
|
+
for (let i = 0; i < s.length; i++) this._bytes.push(s.charCodeAt(i) & 0xFF);
|
|
267
|
+
} else {
|
|
268
|
+
const encoded = new TextEncoder().encode(s);
|
|
269
|
+
this._bytes.push(kUtf8String);
|
|
270
|
+
writeVarint(this._bytes, encoded.length);
|
|
271
|
+
for (let i = 0; i < encoded.length; i++) this._bytes.push(encoded[i]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private _writeBigInt(n: bigint): void {
|
|
276
|
+
this._bytes.push(kBigInt);
|
|
277
|
+
const negative = n < 0n;
|
|
278
|
+
const abs = negative ? -n : n;
|
|
279
|
+
// Encode as little-endian 64-bit words
|
|
280
|
+
const words: number[] = [];
|
|
281
|
+
let remaining = abs;
|
|
282
|
+
while (remaining > 0n) {
|
|
283
|
+
words.push(Number(remaining & 0xFFFFFFFFn));
|
|
284
|
+
words.push(Number((remaining >> 32n) & 0xFFFFFFFFn));
|
|
285
|
+
remaining >>= 64n;
|
|
286
|
+
}
|
|
287
|
+
// bitfield: bits 0 = negative, bits 1+ = word count (as pairs of u32)
|
|
288
|
+
const wordCount = words.length; // already in u32s
|
|
289
|
+
const bitfield = ((wordCount) << 1) | (negative ? 1 : 0);
|
|
290
|
+
writeVarint(this._bytes, bitfield);
|
|
291
|
+
for (const w of words) {
|
|
292
|
+
// write as 4 bytes LE
|
|
293
|
+
this._bytes.push(w & 0xFF);
|
|
294
|
+
this._bytes.push((w >> 8) & 0xFF);
|
|
295
|
+
this._bytes.push((w >> 16) & 0xFF);
|
|
296
|
+
this._bytes.push((w >> 24) & 0xFF);
|
|
297
|
+
}
|
|
298
|
+
if (words.length === 0) {
|
|
299
|
+
// Zero bigint — bitfield already encodes 0 words
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ─── Deserializer ─────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
export class Deserializer {
|
|
307
|
+
protected _pos = 0;
|
|
308
|
+
protected _wireVersion = 0;
|
|
309
|
+
protected _seen: unknown[] = [];
|
|
310
|
+
readonly buffer: Buffer;
|
|
311
|
+
|
|
312
|
+
constructor(buffer: NodeJS.ArrayBufferView | ArrayBuffer) {
|
|
313
|
+
if (!ArrayBuffer.isView(buffer) && !(buffer instanceof ArrayBuffer)) {
|
|
314
|
+
throw new TypeError('buffer must be a TypedArray or a DataView');
|
|
315
|
+
}
|
|
316
|
+
if (buffer instanceof ArrayBuffer) {
|
|
317
|
+
this.buffer = Buffer.from(buffer);
|
|
318
|
+
} else {
|
|
319
|
+
this.buffer = Buffer.from(
|
|
320
|
+
(buffer as NodeJS.ArrayBufferView).buffer,
|
|
321
|
+
(buffer as NodeJS.ArrayBufferView).byteOffset,
|
|
322
|
+
(buffer as NodeJS.ArrayBufferView).byteLength,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
readHeader(): boolean {
|
|
328
|
+
if (this.buffer[this._pos] !== kVersion) {
|
|
329
|
+
throw new Error('ReadHeader() failed');
|
|
330
|
+
}
|
|
331
|
+
this._pos++;
|
|
332
|
+
const [ver, pos] = readVarint(this.buffer, this._pos);
|
|
333
|
+
this._wireVersion = ver;
|
|
334
|
+
this._pos = pos;
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getWireFormatVersion(): number {
|
|
339
|
+
return this._wireVersion;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
readValue(): unknown {
|
|
343
|
+
if (this._pos >= this.buffer.length) {
|
|
344
|
+
throw new Error('Unexpected end of buffer');
|
|
345
|
+
}
|
|
346
|
+
const tag = this.buffer[this._pos++];
|
|
347
|
+
|
|
348
|
+
switch (tag) {
|
|
349
|
+
case kUndefined: return undefined;
|
|
350
|
+
case kNull: return null;
|
|
351
|
+
case kTrue: return true;
|
|
352
|
+
case kFalse: return false;
|
|
353
|
+
|
|
354
|
+
case kInt32: {
|
|
355
|
+
const [n, p] = readVarint(this.buffer, this._pos);
|
|
356
|
+
this._pos = p;
|
|
357
|
+
return zigZagDecode(n);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case kUint32: {
|
|
361
|
+
const [n, p] = readVarint(this.buffer, this._pos);
|
|
362
|
+
this._pos = p;
|
|
363
|
+
return n >>> 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case kDouble: {
|
|
367
|
+
const [d, p] = readFloat64(this.buffer, this._pos);
|
|
368
|
+
this._pos = p;
|
|
369
|
+
return d;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case kBigInt: {
|
|
373
|
+
return this._readBigInt();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
case kOneByteString: {
|
|
377
|
+
const [len, p] = readVarint(this.buffer, this._pos);
|
|
378
|
+
this._pos = p;
|
|
379
|
+
let s = '';
|
|
380
|
+
for (let i = 0; i < len; i++) s += String.fromCharCode(this.buffer[this._pos++]);
|
|
381
|
+
return s;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case kUtf8String: {
|
|
385
|
+
const [len, p] = readVarint(this.buffer, this._pos);
|
|
386
|
+
this._pos = p;
|
|
387
|
+
const bytes = this.buffer.slice(this._pos, this._pos + len);
|
|
388
|
+
this._pos += len;
|
|
389
|
+
return new TextDecoder().decode(bytes);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case kDate: {
|
|
393
|
+
const [ms, p] = readFloat64(this.buffer, this._pos);
|
|
394
|
+
this._pos = p;
|
|
395
|
+
const d = new Date(ms);
|
|
396
|
+
this._seen.push(d);
|
|
397
|
+
return d;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
case kRegExp: {
|
|
401
|
+
const source = this.readValue() as string;
|
|
402
|
+
const [flagBits, p] = readVarint(this.buffer, this._pos);
|
|
403
|
+
this._pos = p;
|
|
404
|
+
const flags = intToRegExpFlags(flagBits);
|
|
405
|
+
const re = new RegExp(source, flags);
|
|
406
|
+
this._seen.push(re);
|
|
407
|
+
return re;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
case kArrayBuffer: {
|
|
411
|
+
const [byteLen, p] = readVarint(this.buffer, this._pos);
|
|
412
|
+
this._pos = p;
|
|
413
|
+
const ab = new ArrayBuffer(byteLen);
|
|
414
|
+
const view = new Uint8Array(ab);
|
|
415
|
+
for (let i = 0; i < byteLen; i++) view[i] = this.buffer[this._pos++];
|
|
416
|
+
this._seen.push(ab);
|
|
417
|
+
return ab;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case kObjectReference: {
|
|
421
|
+
const [idx, p] = readVarint(this.buffer, this._pos);
|
|
422
|
+
this._pos = p;
|
|
423
|
+
if (idx >= this._seen.length) throw new Error('Invalid object reference');
|
|
424
|
+
return this._seen[idx];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
case kBeginDenseArray: {
|
|
428
|
+
const [len, p] = readVarint(this.buffer, this._pos);
|
|
429
|
+
this._pos = p;
|
|
430
|
+
const arr: unknown[] = new Array(len);
|
|
431
|
+
this._seen.push(arr);
|
|
432
|
+
for (let i = 0; i < len; i++) arr[i] = this.readValue();
|
|
433
|
+
// read kEndDenseArray tag
|
|
434
|
+
if (this.buffer[this._pos] !== kEndDenseArray) throw new Error('Expected kEndDenseArray');
|
|
435
|
+
this._pos++;
|
|
436
|
+
// spare properties count (varint)
|
|
437
|
+
const [, p2] = readVarint(this.buffer, this._pos);
|
|
438
|
+
this._pos = p2;
|
|
439
|
+
// length (varint, should match)
|
|
440
|
+
const [, p3] = readVarint(this.buffer, this._pos);
|
|
441
|
+
this._pos = p3;
|
|
442
|
+
return arr;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
case kBeginJSObject: {
|
|
446
|
+
const obj: Record<string, unknown> = {};
|
|
447
|
+
this._seen.push(obj);
|
|
448
|
+
while (this.buffer[this._pos] !== kEndJSObject) {
|
|
449
|
+
const key = this.readValue() as string;
|
|
450
|
+
obj[key] = this.readValue();
|
|
451
|
+
}
|
|
452
|
+
this._pos++; // consume kEndJSObject
|
|
453
|
+
// property count varint
|
|
454
|
+
const [, p] = readVarint(this.buffer, this._pos);
|
|
455
|
+
this._pos = p;
|
|
456
|
+
return obj;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case kHostObject: {
|
|
460
|
+
const obj = this._readHostObject();
|
|
461
|
+
if (obj !== null && obj !== undefined) this._seen.push(obj);
|
|
462
|
+
return obj;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
default:
|
|
466
|
+
throw new Error(`Unknown tag 0x${tag.toString(16).padStart(2, '0')} at position ${this._pos - 1}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
readUint32(): number {
|
|
471
|
+
const [n, p] = readVarint(this.buffer, this._pos);
|
|
472
|
+
this._pos = p;
|
|
473
|
+
return n >>> 0;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
readUint64(): [number, number] {
|
|
477
|
+
const [hi, p1] = readVarint(this.buffer, this._pos);
|
|
478
|
+
const [lo, p2] = readVarint(this.buffer, p1);
|
|
479
|
+
this._pos = p2;
|
|
480
|
+
return [hi >>> 0, lo >>> 0];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
readDouble(): number {
|
|
484
|
+
const [d, p] = readFloat64(this.buffer, this._pos);
|
|
485
|
+
this._pos = p;
|
|
486
|
+
return d;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Returns the byte offset within this.buffer where the raw bytes start.
|
|
490
|
+
// Callers use: new FastBuffer(this.buffer.buffer, this.buffer.byteOffset + offset, length)
|
|
491
|
+
_readRawBytes(length: number): number {
|
|
492
|
+
const offset = this._pos;
|
|
493
|
+
this._pos += length;
|
|
494
|
+
if (this._pos > this.buffer.length) throw new Error('Unexpected end of buffer reading raw bytes');
|
|
495
|
+
return offset;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// readRawBytes is patched on the prototype below (as in Node.js) to return a Buffer slice
|
|
499
|
+
readRawBytes(length: number): Buffer {
|
|
500
|
+
const offset = this._readRawBytes(length);
|
|
501
|
+
return Buffer.from(this.buffer.buffer, this.buffer.byteOffset + offset, length);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_readHostObject(): unknown {
|
|
505
|
+
throw new Error('No host object deserializer installed');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private _readBigInt(): bigint {
|
|
509
|
+
const [bitfield, p] = readVarint(this.buffer, this._pos);
|
|
510
|
+
this._pos = p;
|
|
511
|
+
const negative = (bitfield & 1) === 1;
|
|
512
|
+
const u32Count = (bitfield >> 1);
|
|
513
|
+
let result = 0n;
|
|
514
|
+
for (let i = 0; i < u32Count; i++) {
|
|
515
|
+
const lo = this.buffer[this._pos] |
|
|
516
|
+
(this.buffer[this._pos + 1] << 8) |
|
|
517
|
+
(this.buffer[this._pos + 2] << 16) |
|
|
518
|
+
(this.buffer[this._pos + 3] << 24);
|
|
519
|
+
this._pos += 4;
|
|
520
|
+
result |= BigInt(lo >>> 0) << BigInt(i * 32);
|
|
521
|
+
}
|
|
522
|
+
return negative ? -result : result;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ─── RegExp flags encoding ────────────────────────────────────────────────────
|
|
527
|
+
|
|
528
|
+
// Matches V8's RegExpFlags enum bit layout
|
|
529
|
+
function regExpFlagsToInt(re: RegExp): number {
|
|
530
|
+
let flags = 0;
|
|
531
|
+
if (re.global) flags |= 1 << 0;
|
|
532
|
+
if (re.ignoreCase) flags |= 1 << 1;
|
|
533
|
+
if (re.multiline) flags |= 1 << 2;
|
|
534
|
+
if (re.sticky) flags |= 1 << 3;
|
|
535
|
+
if (re.unicode) flags |= 1 << 4;
|
|
536
|
+
if (re.dotAll) flags |= 1 << 5;
|
|
537
|
+
if (re.hasIndices) flags |= 1 << 6;
|
|
538
|
+
if (re.unicodeSets) flags |= 1 << 7;
|
|
539
|
+
return flags;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function intToRegExpFlags(bits: number): string {
|
|
543
|
+
let flags = '';
|
|
544
|
+
if (bits & (1 << 0)) flags += 'g';
|
|
545
|
+
if (bits & (1 << 1)) flags += 'i';
|
|
546
|
+
if (bits & (1 << 2)) flags += 'm';
|
|
547
|
+
if (bits & (1 << 3)) flags += 'y';
|
|
548
|
+
if (bits & (1 << 4)) flags += 'u';
|
|
549
|
+
if (bits & (1 << 5)) flags += 's';
|
|
550
|
+
if (bits & (1 << 6)) flags += 'd';
|
|
551
|
+
if (bits & (1 << 7)) flags += 'v';
|
|
552
|
+
return flags;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ─── DefaultSerializer / DefaultDeserializer ─────────────────────────────────
|
|
556
|
+
|
|
557
|
+
export class DefaultSerializer extends Serializer {
|
|
558
|
+
constructor() {
|
|
559
|
+
super();
|
|
560
|
+
this._setTreatArrayBufferViewsAsHostObjects(true);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
override _writeHostObject(abView: NodeJS.ArrayBufferView): void {
|
|
564
|
+
let typeIndex: number;
|
|
565
|
+
if (Buffer.isBuffer(abView)) {
|
|
566
|
+
typeIndex = 10; // FastBuffer / Buffer
|
|
567
|
+
} else {
|
|
568
|
+
typeIndex = typedArrayToIndex(abView);
|
|
569
|
+
if (typeIndex === -1) {
|
|
570
|
+
throw new (this._getDataCloneError)(
|
|
571
|
+
`Unserializable host object: ${Object.prototype.toString.call(abView)}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
this.writeUint32(typeIndex);
|
|
576
|
+
this.writeUint32(abView.byteLength);
|
|
577
|
+
this.writeRawBytes(new Uint8Array(abView.buffer, abView.byteOffset, abView.byteLength));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export class DefaultDeserializer extends Deserializer {
|
|
582
|
+
override _readHostObject(): NodeJS.ArrayBufferView {
|
|
583
|
+
const typeIndex = this.readUint32();
|
|
584
|
+
const byteLength = this.readUint32();
|
|
585
|
+
const byteOffset = this._readRawBytes(byteLength);
|
|
586
|
+
|
|
587
|
+
if (typeIndex === 10) {
|
|
588
|
+
// Buffer
|
|
589
|
+
const b = Buffer.allocUnsafe(byteLength);
|
|
590
|
+
this.buffer.copy(b, 0, this.buffer.byteOffset + byteOffset, this.buffer.byteOffset + byteOffset + byteLength);
|
|
591
|
+
return b;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const Ctor = indexToTypedArray(typeIndex);
|
|
595
|
+
if (!Ctor) throw new Error(`Unknown TypedArray type index ${typeIndex}`);
|
|
596
|
+
|
|
597
|
+
const bytesPerElement = (Ctor as unknown as { BYTES_PER_ELEMENT?: number }).BYTES_PER_ELEMENT ?? 1;
|
|
598
|
+
const elementCount = byteLength / bytesPerElement;
|
|
599
|
+
const absoluteOffset = this.buffer.byteOffset + byteOffset;
|
|
600
|
+
|
|
601
|
+
if (absoluteOffset % bytesPerElement === 0) {
|
|
602
|
+
return new Ctor(this.buffer.buffer as ArrayBuffer, absoluteOffset, elementCount);
|
|
603
|
+
}
|
|
604
|
+
// Unaligned — copy to aligned buffer
|
|
605
|
+
const copy = Buffer.allocUnsafe(byteLength);
|
|
606
|
+
this.buffer.copy(copy, 0, this.buffer.byteOffset + byteOffset, this.buffer.byteOffset + byteOffset + byteLength);
|
|
607
|
+
return new Ctor(copy.buffer as ArrayBuffer, copy.byteOffset, elementCount);
|
|
608
|
+
}
|
|
609
|
+
}
|