codeflash 0.0.1 → 0.1.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.
@@ -0,0 +1,851 @@
1
+ /**
2
+ * Codeflash Universal Serializer
3
+ *
4
+ * A robust serialization system for JavaScript values that:
5
+ * 1. Prefers V8 serialization (Node.js native) - fastest, handles all JS types
6
+ * 2. Falls back to msgpack with custom extensions (for Bun/browser environments)
7
+ *
8
+ * Supports:
9
+ * - All primitive types (null, undefined, boolean, number, string, bigint, symbol)
10
+ * - Special numbers (NaN, Infinity, -Infinity)
11
+ * - Objects, Arrays (including sparse arrays)
12
+ * - Map, Set, WeakMap references, WeakSet references
13
+ * - Date, RegExp, Error (and subclasses)
14
+ * - TypedArrays (Int8Array, Uint8Array, Float32Array, etc.)
15
+ * - ArrayBuffer, SharedArrayBuffer, DataView
16
+ * - Circular references
17
+ * - Functions (by reference/name only)
18
+ *
19
+ * Usage:
20
+ * const { serialize, deserialize, getSerializerType } = require('./codeflash-serializer');
21
+ *
22
+ * const buffer = serialize(value);
23
+ * const restored = deserialize(buffer);
24
+ */
25
+
26
+ 'use strict';
27
+
28
+ // ============================================================================
29
+ // SERIALIZER DETECTION
30
+ // ============================================================================
31
+
32
+ let useV8 = false;
33
+ let v8Module = null;
34
+
35
+ // Try to load V8 module (available in Node.js)
36
+ try {
37
+ v8Module = require('v8');
38
+ // Verify serialize/deserialize are available
39
+ if (typeof v8Module.serialize === 'function' && typeof v8Module.deserialize === 'function') {
40
+ // Perform a self-test to verify V8 serialization works correctly
41
+ // This catches cases like Jest's VM context where V8 serialization
42
+ // produces data that deserializes incorrectly (Maps become plain objects)
43
+ const testMap = new Map([['__test__', 1]]);
44
+ const testBuffer = v8Module.serialize(testMap);
45
+ const testRestored = v8Module.deserialize(testBuffer);
46
+
47
+ if (testRestored instanceof Map && testRestored.get('__test__') === 1) {
48
+ useV8 = true;
49
+ } else {
50
+ // V8 serialization is broken in this environment (e.g., Jest)
51
+ useV8 = false;
52
+ }
53
+ }
54
+ } catch (e) {
55
+ // V8 not available (Bun, browser, etc.)
56
+ }
57
+
58
+ // Load msgpack as fallback
59
+ let msgpack = null;
60
+ try {
61
+ msgpack = require('@msgpack/msgpack');
62
+ } catch (e) {
63
+ // msgpack not installed
64
+ }
65
+
66
+ /**
67
+ * Get the serializer type being used.
68
+ * @returns {string} - 'v8' or 'msgpack'
69
+ */
70
+ function getSerializerType() {
71
+ return useV8 ? 'v8' : 'msgpack';
72
+ }
73
+
74
+ // ============================================================================
75
+ // V8 SERIALIZATION (PRIMARY)
76
+ // ============================================================================
77
+
78
+ /**
79
+ * Serialize a value using V8's native serialization.
80
+ * This handles all JavaScript types including:
81
+ * - Primitives, Objects, Arrays
82
+ * - Map, Set, Date, RegExp, Error
83
+ * - TypedArrays, ArrayBuffer
84
+ * - Circular references
85
+ *
86
+ * @param {any} value - Value to serialize
87
+ * @returns {Buffer} - Serialized buffer
88
+ */
89
+ function serializeV8(value) {
90
+ try {
91
+ return v8Module.serialize(value);
92
+ } catch (e) {
93
+ // V8 can't serialize some things (functions, symbols in some contexts)
94
+ // Fall back to wrapped serialization
95
+ return v8Module.serialize(wrapForV8(value));
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Deserialize a V8-serialized buffer.
101
+ *
102
+ * @param {Buffer} buffer - Serialized buffer
103
+ * @returns {any} - Deserialized value
104
+ */
105
+ function deserializeV8(buffer) {
106
+ const value = v8Module.deserialize(buffer);
107
+ return unwrapFromV8(value);
108
+ }
109
+
110
+ /**
111
+ * Wrap values that V8 can't serialize natively.
112
+ * V8 can't serialize: functions, symbols (in some cases)
113
+ */
114
+ function wrapForV8(value, seen = new WeakMap()) {
115
+ if (value === null || value === undefined) return value;
116
+
117
+ const type = typeof value;
118
+
119
+ // Primitives that V8 handles
120
+ if (type === 'number' || type === 'string' || type === 'boolean' || type === 'bigint') {
121
+ return value;
122
+ }
123
+
124
+ // Symbols - wrap with marker
125
+ if (type === 'symbol') {
126
+ return { __codeflash_type__: 'Symbol', description: value.description };
127
+ }
128
+
129
+ // Functions - wrap with marker
130
+ if (type === 'function') {
131
+ return {
132
+ __codeflash_type__: 'Function',
133
+ name: value.name || 'anonymous',
134
+ // Can't serialize function body reliably
135
+ };
136
+ }
137
+
138
+ // Objects
139
+ if (type === 'object') {
140
+ // Check for circular reference
141
+ if (seen.has(value)) {
142
+ return seen.get(value);
143
+ }
144
+
145
+ // V8 handles most objects natively
146
+ // Just need to recurse into arrays and plain objects to wrap nested functions/symbols
147
+
148
+ if (Array.isArray(value)) {
149
+ const wrapped = [];
150
+ seen.set(value, wrapped);
151
+ for (let i = 0; i < value.length; i++) {
152
+ if (i in value) {
153
+ wrapped[i] = wrapForV8(value[i], seen);
154
+ }
155
+ }
156
+ return wrapped;
157
+ }
158
+
159
+ // V8 handles these natively
160
+ if (value instanceof Date || value instanceof RegExp || value instanceof Error ||
161
+ value instanceof Map || value instanceof Set ||
162
+ ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
163
+ return value;
164
+ }
165
+
166
+ // Plain objects - recurse
167
+ const wrapped = {};
168
+ seen.set(value, wrapped);
169
+ for (const key of Object.keys(value)) {
170
+ wrapped[key] = wrapForV8(value[key], seen);
171
+ }
172
+ return wrapped;
173
+ }
174
+
175
+ return value;
176
+ }
177
+
178
+ /**
179
+ * Unwrap values that were wrapped for V8 serialization.
180
+ */
181
+ function unwrapFromV8(value, seen = new WeakMap()) {
182
+ if (value === null || value === undefined) return value;
183
+
184
+ const type = typeof value;
185
+
186
+ if (type !== 'object') return value;
187
+
188
+ // Check for circular reference
189
+ if (seen.has(value)) {
190
+ return seen.get(value);
191
+ }
192
+
193
+ // Check for wrapped types
194
+ if (value.__codeflash_type__) {
195
+ switch (value.__codeflash_type__) {
196
+ case 'Symbol':
197
+ return Symbol(value.description);
198
+ case 'Function':
199
+ // Can't restore function body, return a placeholder
200
+ const fn = function() { throw new Error(`Deserialized function placeholder: ${value.name}`); };
201
+ Object.defineProperty(fn, 'name', { value: value.name });
202
+ return fn;
203
+ default:
204
+ // Unknown wrapped type, return as-is
205
+ return value;
206
+ }
207
+ }
208
+
209
+ // Arrays
210
+ if (Array.isArray(value)) {
211
+ const unwrapped = [];
212
+ seen.set(value, unwrapped);
213
+ for (let i = 0; i < value.length; i++) {
214
+ if (i in value) {
215
+ unwrapped[i] = unwrapFromV8(value[i], seen);
216
+ }
217
+ }
218
+ return unwrapped;
219
+ }
220
+
221
+ // V8 restores these natively
222
+ if (value instanceof Date || value instanceof RegExp || value instanceof Error ||
223
+ value instanceof Map || value instanceof Set ||
224
+ ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
225
+ return value;
226
+ }
227
+
228
+ // Plain objects - recurse
229
+ const unwrapped = {};
230
+ seen.set(value, unwrapped);
231
+ for (const key of Object.keys(value)) {
232
+ unwrapped[key] = unwrapFromV8(value[key], seen);
233
+ }
234
+ return unwrapped;
235
+ }
236
+
237
+ // ============================================================================
238
+ // MSGPACK SERIALIZATION (FALLBACK)
239
+ // ============================================================================
240
+
241
+ /**
242
+ * Extension type IDs for msgpack.
243
+ * Using negative IDs to avoid conflicts with user-defined extensions.
244
+ */
245
+ const EXT_TYPES = {
246
+ UNDEFINED: 0x01,
247
+ NAN: 0x02,
248
+ INFINITY_POS: 0x03,
249
+ INFINITY_NEG: 0x04,
250
+ BIGINT: 0x05,
251
+ SYMBOL: 0x06,
252
+ DATE: 0x07,
253
+ REGEXP: 0x08,
254
+ ERROR: 0x09,
255
+ MAP: 0x0A,
256
+ SET: 0x0B,
257
+ INT8ARRAY: 0x10,
258
+ UINT8ARRAY: 0x11,
259
+ UINT8CLAMPEDARRAY: 0x12,
260
+ INT16ARRAY: 0x13,
261
+ UINT16ARRAY: 0x14,
262
+ INT32ARRAY: 0x15,
263
+ UINT32ARRAY: 0x16,
264
+ FLOAT32ARRAY: 0x17,
265
+ FLOAT64ARRAY: 0x18,
266
+ BIGINT64ARRAY: 0x19,
267
+ BIGUINT64ARRAY: 0x1A,
268
+ ARRAYBUFFER: 0x1B,
269
+ DATAVIEW: 0x1C,
270
+ FUNCTION: 0x1D,
271
+ CIRCULAR_REF: 0x1E,
272
+ SPARSE_ARRAY: 0x1F,
273
+ };
274
+
275
+ /**
276
+ * Create msgpack extension codec for JavaScript types.
277
+ */
278
+ function createMsgpackCodec() {
279
+ const extensionCodec = new msgpack.ExtensionCodec();
280
+
281
+ // Undefined
282
+ extensionCodec.register({
283
+ type: EXT_TYPES.UNDEFINED,
284
+ encode: (value) => {
285
+ if (value === undefined) return new Uint8Array(0);
286
+ return null;
287
+ },
288
+ decode: () => undefined,
289
+ });
290
+
291
+ // NaN
292
+ extensionCodec.register({
293
+ type: EXT_TYPES.NAN,
294
+ encode: (value) => {
295
+ if (typeof value === 'number' && Number.isNaN(value)) return new Uint8Array(0);
296
+ return null;
297
+ },
298
+ decode: () => NaN,
299
+ });
300
+
301
+ // Positive Infinity
302
+ extensionCodec.register({
303
+ type: EXT_TYPES.INFINITY_POS,
304
+ encode: (value) => {
305
+ if (value === Infinity) return new Uint8Array(0);
306
+ return null;
307
+ },
308
+ decode: () => Infinity,
309
+ });
310
+
311
+ // Negative Infinity
312
+ extensionCodec.register({
313
+ type: EXT_TYPES.INFINITY_NEG,
314
+ encode: (value) => {
315
+ if (value === -Infinity) return new Uint8Array(0);
316
+ return null;
317
+ },
318
+ decode: () => -Infinity,
319
+ });
320
+
321
+ // BigInt
322
+ extensionCodec.register({
323
+ type: EXT_TYPES.BIGINT,
324
+ encode: (value) => {
325
+ if (typeof value === 'bigint') {
326
+ const str = value.toString();
327
+ return new TextEncoder().encode(str);
328
+ }
329
+ return null;
330
+ },
331
+ decode: (data) => {
332
+ const str = new TextDecoder().decode(data);
333
+ return BigInt(str);
334
+ },
335
+ });
336
+
337
+ // Symbol
338
+ extensionCodec.register({
339
+ type: EXT_TYPES.SYMBOL,
340
+ encode: (value) => {
341
+ if (typeof value === 'symbol') {
342
+ // Distinguish between undefined description and empty string
343
+ // Use a special marker for undefined description
344
+ const desc = value.description;
345
+ if (desc === undefined) {
346
+ return new TextEncoder().encode('\x00__UNDEF__');
347
+ }
348
+ return new TextEncoder().encode(desc);
349
+ }
350
+ return null;
351
+ },
352
+ decode: (data) => {
353
+ const description = new TextDecoder().decode(data);
354
+ // Check for undefined marker
355
+ if (description === '\x00__UNDEF__') {
356
+ return Symbol();
357
+ }
358
+ return Symbol(description);
359
+ },
360
+ });
361
+
362
+ // Note: Date is handled via marker objects in prepareForMsgpack/restoreFromMsgpack
363
+ // because msgpack's built-in timestamp extension doesn't properly handle NaN (Invalid Date)
364
+
365
+ // RegExp - use Object.prototype.toString for cross-context detection
366
+ extensionCodec.register({
367
+ type: EXT_TYPES.REGEXP,
368
+ encode: (value) => {
369
+ if (Object.prototype.toString.call(value) === '[object RegExp]') {
370
+ const obj = { source: value.source, flags: value.flags };
371
+ return msgpack.encode(obj);
372
+ }
373
+ return null;
374
+ },
375
+ decode: (data) => {
376
+ const obj = msgpack.decode(data);
377
+ return new RegExp(obj.source, obj.flags);
378
+ },
379
+ });
380
+
381
+ // Error - use Object.prototype.toString for cross-context detection
382
+ extensionCodec.register({
383
+ type: EXT_TYPES.ERROR,
384
+ encode: (value) => {
385
+ // Check for Error-like objects (cross-VM-context compatible)
386
+ if (Object.prototype.toString.call(value) === '[object Error]' ||
387
+ (value && value.name && value.message !== undefined && value.stack !== undefined)) {
388
+ const obj = {
389
+ name: value.name,
390
+ message: value.message,
391
+ stack: value.stack,
392
+ // Include custom properties
393
+ ...Object.fromEntries(
394
+ Object.entries(value).filter(([k]) => !['name', 'message', 'stack'].includes(k))
395
+ ),
396
+ };
397
+ return msgpack.encode(obj);
398
+ }
399
+ return null;
400
+ },
401
+ decode: (data) => {
402
+ const obj = msgpack.decode(data);
403
+ let ErrorClass = Error;
404
+ // Try to use the appropriate error class
405
+ const errorClasses = {
406
+ TypeError, RangeError, SyntaxError, ReferenceError,
407
+ URIError, EvalError, Error
408
+ };
409
+ if (obj.name in errorClasses) {
410
+ ErrorClass = errorClasses[obj.name];
411
+ }
412
+ const error = new ErrorClass(obj.message);
413
+ error.stack = obj.stack;
414
+ // Restore custom properties
415
+ for (const [key, val] of Object.entries(obj)) {
416
+ if (!['name', 'message', 'stack'].includes(key)) {
417
+ error[key] = val;
418
+ }
419
+ }
420
+ return error;
421
+ },
422
+ });
423
+
424
+ // Function (limited - can't serialize body)
425
+ extensionCodec.register({
426
+ type: EXT_TYPES.FUNCTION,
427
+ encode: (value) => {
428
+ if (typeof value === 'function') {
429
+ return new TextEncoder().encode(value.name || 'anonymous');
430
+ }
431
+ return null;
432
+ },
433
+ decode: (data) => {
434
+ const name = new TextDecoder().decode(data);
435
+ const fn = function() { throw new Error(`Deserialized function placeholder: ${name}`); };
436
+ Object.defineProperty(fn, 'name', { value: name });
437
+ return fn;
438
+ },
439
+ });
440
+
441
+ return extensionCodec;
442
+ }
443
+
444
+ // Singleton codec instance
445
+ let msgpackCodec = null;
446
+
447
+ function getMsgpackCodec() {
448
+ if (!msgpackCodec && msgpack) {
449
+ msgpackCodec = createMsgpackCodec();
450
+ }
451
+ return msgpackCodec;
452
+ }
453
+
454
+ /**
455
+ * Prepare a value for msgpack serialization.
456
+ * Handles types that need special treatment beyond extensions.
457
+ */
458
+ function prepareForMsgpack(value, seen = new Map(), refId = { current: 0 }) {
459
+ if (value === null) return null;
460
+ // undefined needs special handling because msgpack converts it to null
461
+ if (value === undefined) return { __codeflash_undefined__: true };
462
+
463
+ const type = typeof value;
464
+
465
+ // Special number values that msgpack doesn't handle correctly
466
+ if (type === 'number') {
467
+ if (Number.isNaN(value)) return { __codeflash_nan__: true };
468
+ if (value === Infinity) return { __codeflash_infinity__: true };
469
+ if (value === -Infinity) return { __codeflash_neg_infinity__: true };
470
+ return value;
471
+ }
472
+
473
+ // Primitives that msgpack handles or our extensions handle
474
+ if (type === 'string' || type === 'boolean' ||
475
+ type === 'bigint' || type === 'symbol' || type === 'function') {
476
+ return value;
477
+ }
478
+
479
+ if (type !== 'object') return value;
480
+
481
+ // Check for circular reference
482
+ if (seen.has(value)) {
483
+ return { __codeflash_circular__: seen.get(value) };
484
+ }
485
+
486
+ // Assign reference ID for potential circular refs
487
+ const id = refId.current++;
488
+ seen.set(value, id);
489
+
490
+ // Use toString for cross-VM-context type detection
491
+ const tag = Object.prototype.toString.call(value);
492
+
493
+ // Date - handle specially because msgpack's built-in timestamp doesn't handle NaN
494
+ if (tag === '[object Date]') {
495
+ const time = value.getTime();
496
+ // Store as marker object with the timestamp
497
+ // We use a string representation to preserve NaN
498
+ return {
499
+ __codeflash_date__: Number.isNaN(time) ? '__NAN__' : time,
500
+ __id__: id,
501
+ };
502
+ }
503
+
504
+ // RegExp, Error - handled by extensions
505
+ if (tag === '[object RegExp]' || tag === '[object Error]') {
506
+ return value;
507
+ }
508
+
509
+ // Map (use toString for cross-VM-context)
510
+ if (tag === '[object Map]') {
511
+ const entries = [];
512
+ for (const [k, v] of value) {
513
+ entries.push([prepareForMsgpack(k, seen, refId), prepareForMsgpack(v, seen, refId)]);
514
+ }
515
+ return { __codeflash_map__: entries, __id__: id };
516
+ }
517
+
518
+ // Set (use toString for cross-VM-context)
519
+ if (tag === '[object Set]') {
520
+ const values = [];
521
+ for (const v of value) {
522
+ values.push(prepareForMsgpack(v, seen, refId));
523
+ }
524
+ return { __codeflash_set__: values, __id__: id };
525
+ }
526
+
527
+ // TypedArrays (use ArrayBuffer.isView which works cross-context)
528
+ if (ArrayBuffer.isView(value) && tag !== '[object DataView]') {
529
+ return {
530
+ __codeflash_typedarray__: value.constructor.name,
531
+ data: Array.from(value),
532
+ __id__: id,
533
+ };
534
+ }
535
+
536
+ // DataView (use toString for cross-VM-context)
537
+ if (tag === '[object DataView]') {
538
+ return {
539
+ __codeflash_dataview__: true,
540
+ data: Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)),
541
+ __id__: id,
542
+ };
543
+ }
544
+
545
+ // ArrayBuffer (use toString for cross-VM-context)
546
+ if (tag === '[object ArrayBuffer]') {
547
+ return {
548
+ __codeflash_arraybuffer__: true,
549
+ data: Array.from(new Uint8Array(value)),
550
+ __id__: id,
551
+ };
552
+ }
553
+
554
+ // Arrays - always wrap in marker to preserve __id__ for circular references
555
+ // (msgpack doesn't preserve non-numeric properties on arrays)
556
+ if (Array.isArray(value)) {
557
+ const isSparse = value.length > 0 && Object.keys(value).length !== value.length;
558
+ if (isSparse) {
559
+ // Sparse array - store as object with indices
560
+ const sparse = { __codeflash_sparse_array__: true, length: value.length, elements: {}, __id__: id };
561
+ for (const key of Object.keys(value)) {
562
+ sparse.elements[key] = prepareForMsgpack(value[key], seen, refId);
563
+ }
564
+ return sparse;
565
+ }
566
+ // Dense array - wrap in marker object to preserve __id__
567
+ const elements = [];
568
+ for (let i = 0; i < value.length; i++) {
569
+ elements[i] = prepareForMsgpack(value[i], seen, refId);
570
+ }
571
+ return { __codeflash_array__: elements, __id__: id };
572
+ }
573
+
574
+ // Plain objects
575
+ const obj = { __id__: id };
576
+ for (const key of Object.keys(value)) {
577
+ obj[key] = prepareForMsgpack(value[key], seen, refId);
578
+ }
579
+ return obj;
580
+ }
581
+
582
+ /**
583
+ * Restore a value after msgpack deserialization.
584
+ */
585
+ function restoreFromMsgpack(value, refs = new Map()) {
586
+ if (value === null || value === undefined) return value;
587
+
588
+ const type = typeof value;
589
+ if (type !== 'object') return value;
590
+
591
+ // Built-in types that msgpack handles via extensions - return as-is
592
+ // These should NOT be treated as plain objects (use toString for cross-VM-context)
593
+ // Note: Date is handled via marker objects, so not included here
594
+ const tag = Object.prototype.toString.call(value);
595
+ if (tag === '[object RegExp]' || tag === '[object Error]') {
596
+ return value;
597
+ }
598
+
599
+ // Special value markers
600
+ if (value.__codeflash_undefined__) return undefined;
601
+ if (value.__codeflash_nan__) return NaN;
602
+ if (value.__codeflash_infinity__) return Infinity;
603
+ if (value.__codeflash_neg_infinity__) return -Infinity;
604
+
605
+ // Date marker
606
+ if (value.__codeflash_date__ !== undefined) {
607
+ const time = value.__codeflash_date__ === '__NAN__' ? NaN : value.__codeflash_date__;
608
+ const date = new Date(time);
609
+ const id = value.__id__;
610
+ if (id !== undefined) refs.set(id, date);
611
+ return date;
612
+ }
613
+
614
+ // Check for circular reference marker
615
+ if (value.__codeflash_circular__ !== undefined) {
616
+ return refs.get(value.__codeflash_circular__);
617
+ }
618
+
619
+ // Store reference if this object has an ID
620
+ const id = value.__id__;
621
+
622
+ // Map
623
+ if (value.__codeflash_map__) {
624
+ const map = new Map();
625
+ if (id !== undefined) refs.set(id, map);
626
+ for (const [k, v] of value.__codeflash_map__) {
627
+ map.set(restoreFromMsgpack(k, refs), restoreFromMsgpack(v, refs));
628
+ }
629
+ return map;
630
+ }
631
+
632
+ // Set
633
+ if (value.__codeflash_set__) {
634
+ const set = new Set();
635
+ if (id !== undefined) refs.set(id, set);
636
+ for (const v of value.__codeflash_set__) {
637
+ set.add(restoreFromMsgpack(v, refs));
638
+ }
639
+ return set;
640
+ }
641
+
642
+ // TypedArrays
643
+ if (value.__codeflash_typedarray__) {
644
+ const TypedArrayClass = globalThis[value.__codeflash_typedarray__];
645
+ if (TypedArrayClass) {
646
+ const arr = new TypedArrayClass(value.data);
647
+ if (id !== undefined) refs.set(id, arr);
648
+ return arr;
649
+ }
650
+ }
651
+
652
+ // DataView
653
+ if (value.__codeflash_dataview__) {
654
+ const buffer = new ArrayBuffer(value.data.length);
655
+ new Uint8Array(buffer).set(value.data);
656
+ const view = new DataView(buffer);
657
+ if (id !== undefined) refs.set(id, view);
658
+ return view;
659
+ }
660
+
661
+ // ArrayBuffer
662
+ if (value.__codeflash_arraybuffer__) {
663
+ const buffer = new ArrayBuffer(value.data.length);
664
+ new Uint8Array(buffer).set(value.data);
665
+ if (id !== undefined) refs.set(id, buffer);
666
+ return buffer;
667
+ }
668
+
669
+ // Dense array marker
670
+ if (value.__codeflash_array__) {
671
+ const arr = [];
672
+ if (id !== undefined) refs.set(id, arr);
673
+ const elements = value.__codeflash_array__;
674
+ for (let i = 0; i < elements.length; i++) {
675
+ arr[i] = restoreFromMsgpack(elements[i], refs);
676
+ }
677
+ return arr;
678
+ }
679
+
680
+ // Sparse array
681
+ if (value.__codeflash_sparse_array__) {
682
+ const arr = new Array(value.length);
683
+ if (id !== undefined) refs.set(id, arr);
684
+ for (const [key, val] of Object.entries(value.elements)) {
685
+ arr[parseInt(key, 10)] = restoreFromMsgpack(val, refs);
686
+ }
687
+ return arr;
688
+ }
689
+
690
+ // Arrays (legacy - shouldn't happen with new format, but keep for safety)
691
+ if (Array.isArray(value)) {
692
+ const arr = [];
693
+ if (id !== undefined) refs.set(id, arr);
694
+ for (let i = 0; i < value.length; i++) {
695
+ if (i in value) {
696
+ arr[i] = restoreFromMsgpack(value[i], refs);
697
+ }
698
+ }
699
+ return arr;
700
+ }
701
+
702
+ // Plain objects - remove __id__ from result
703
+ const obj = {};
704
+ if (id !== undefined) refs.set(id, obj);
705
+ for (const [key, val] of Object.entries(value)) {
706
+ if (key !== '__id__') {
707
+ obj[key] = restoreFromMsgpack(val, refs);
708
+ }
709
+ }
710
+ return obj;
711
+ }
712
+
713
+ /**
714
+ * Serialize a value using msgpack with extensions.
715
+ *
716
+ * @param {any} value - Value to serialize
717
+ * @returns {Buffer} - Serialized buffer
718
+ */
719
+ function serializeMsgpack(value) {
720
+ if (!msgpack) {
721
+ throw new Error('msgpack not available and V8 serialization not available');
722
+ }
723
+
724
+ const codec = getMsgpackCodec();
725
+ const prepared = prepareForMsgpack(value);
726
+ const encoded = msgpack.encode(prepared, { extensionCodec: codec });
727
+ return Buffer.from(encoded);
728
+ }
729
+
730
+ /**
731
+ * Deserialize a msgpack-serialized buffer.
732
+ *
733
+ * @param {Buffer|Uint8Array} buffer - Serialized buffer
734
+ * @returns {any} - Deserialized value
735
+ */
736
+ function deserializeMsgpack(buffer) {
737
+ if (!msgpack) {
738
+ throw new Error('msgpack not available');
739
+ }
740
+
741
+ const codec = getMsgpackCodec();
742
+ const decoded = msgpack.decode(buffer, { extensionCodec: codec });
743
+ return restoreFromMsgpack(decoded);
744
+ }
745
+
746
+ // ============================================================================
747
+ // PUBLIC API
748
+ // ============================================================================
749
+
750
+ /**
751
+ * Serialize a value using the best available method.
752
+ * Prefers V8 serialization, falls back to msgpack.
753
+ *
754
+ * @param {any} value - Value to serialize
755
+ * @returns {Buffer} - Serialized buffer with format marker
756
+ */
757
+ function serialize(value) {
758
+ // Add a format marker byte at the start
759
+ // 0x01 = V8, 0x02 = msgpack
760
+ if (useV8) {
761
+ const serialized = serializeV8(value);
762
+ const result = Buffer.allocUnsafe(serialized.length + 1);
763
+ result[0] = 0x01;
764
+ serialized.copy(result, 1);
765
+ return result;
766
+ } else {
767
+ const serialized = serializeMsgpack(value);
768
+ const result = Buffer.allocUnsafe(serialized.length + 1);
769
+ result[0] = 0x02;
770
+ serialized.copy(result, 1);
771
+ return result;
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Deserialize a buffer that was serialized with serialize().
777
+ * Automatically detects the format from the marker byte.
778
+ *
779
+ * @param {Buffer|Uint8Array} buffer - Serialized buffer
780
+ * @returns {any} - Deserialized value
781
+ */
782
+ function deserialize(buffer) {
783
+ if (!buffer || buffer.length === 0) {
784
+ throw new Error('Empty buffer cannot be deserialized');
785
+ }
786
+
787
+ const format = buffer[0];
788
+ const data = buffer.slice(1);
789
+
790
+ if (format === 0x01) {
791
+ // V8 format
792
+ if (!useV8) {
793
+ throw new Error('Buffer was serialized with V8 but V8 is not available');
794
+ }
795
+ return deserializeV8(data);
796
+ } else if (format === 0x02) {
797
+ // msgpack format
798
+ return deserializeMsgpack(data);
799
+ } else {
800
+ throw new Error(`Unknown serialization format: ${format}`);
801
+ }
802
+ }
803
+
804
+ /**
805
+ * Force serialization using a specific method.
806
+ * Useful for testing or cross-environment compatibility.
807
+ */
808
+ const serializeWith = {
809
+ v8: useV8 ? (value) => {
810
+ const serialized = serializeV8(value);
811
+ const result = Buffer.allocUnsafe(serialized.length + 1);
812
+ result[0] = 0x01;
813
+ serialized.copy(result, 1);
814
+ return result;
815
+ } : null,
816
+
817
+ msgpack: msgpack ? (value) => {
818
+ const serialized = serializeMsgpack(value);
819
+ const result = Buffer.allocUnsafe(serialized.length + 1);
820
+ result[0] = 0x02;
821
+ serialized.copy(result, 1);
822
+ return result;
823
+ } : null,
824
+ };
825
+
826
+ // ============================================================================
827
+ // EXPORTS
828
+ // ============================================================================
829
+
830
+ module.exports = {
831
+ // Main API
832
+ serialize,
833
+ deserialize,
834
+ getSerializerType,
835
+
836
+ // Force specific serializer
837
+ serializeWith,
838
+
839
+ // Low-level (for testing)
840
+ serializeV8: useV8 ? serializeV8 : null,
841
+ deserializeV8: useV8 ? deserializeV8 : null,
842
+ serializeMsgpack: msgpack ? serializeMsgpack : null,
843
+ deserializeMsgpack: msgpack ? deserializeMsgpack : null,
844
+
845
+ // Feature detection
846
+ hasV8: useV8,
847
+ hasMsgpack: !!msgpack,
848
+
849
+ // Extension types (for reference)
850
+ EXT_TYPES,
851
+ };