msgpackr 1.11.11 → 1.11.13

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/dist/node.cjs CHANGED
@@ -2406,9 +2406,16 @@ const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
2406
2406
  return textEncoder.encodeInto(string, target.subarray(position)).written
2407
2407
  } : false;
2408
2408
  setWriteStructSlots(writeStruct, prepareStructures);
2409
- function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr) {
2409
+ function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
2410
2410
  let typedStructs = packr.typedStructs || (packr.typedStructs = []);
2411
2411
  // note that we rely on pack.js to load stored structures before we get to this point
2412
+ // structureKnown is set only on the internal layout-retry below: attempt 1 already minted
2413
+ // this record's structure, so the retry re-encodes a known shape and must not re-apply the
2414
+ // cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
2415
+ // `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
2416
+ // re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
2417
+ const cap = packr.maxOwnStructures ?? Infinity;
2418
+ const frozen = !structureKnown && typedStructs.length >= cap;
2412
2419
  let targetView = target.dataView;
2413
2420
  let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
2414
2421
  let safeEnd = target.length - 10;
@@ -2439,9 +2446,12 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2439
2446
  let usedAscii0;
2440
2447
  let keyIndex = 0;
2441
2448
  for (let key in object) {
2442
- let value = object[key];
2443
2449
  let nextTransition = transition[key];
2450
+ // Resolve the key transition BEFORE reading the value: when frozen and the key is new we
2451
+ // bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
2452
+ // then again by the plain fallback (which would double-read a side-effecting accessor).
2444
2453
  if (!nextTransition) {
2454
+ if (frozen) return 0;
2445
2455
  transition[key] = nextTransition = {
2446
2456
  key,
2447
2457
  parent: transition,
@@ -2456,6 +2466,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2456
2466
  date64: null
2457
2467
  };
2458
2468
  }
2469
+ let value = object[key];
2459
2470
  if (position > safeEnd) {
2460
2471
  target = makeRoom(position);
2461
2472
  targetView = target.dataView;
@@ -2473,10 +2484,10 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2473
2484
  if (nextId < 200 || !nextTransition.num64) {
2474
2485
  if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
2475
2486
  if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
2476
- transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1);
2487
+ transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
2477
2488
  target[position++] = number;
2478
2489
  } else {
2479
- transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
2490
+ transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
2480
2491
  targetView.setUint32(position, number, true);
2481
2492
  position += 4;
2482
2493
  }
@@ -2487,14 +2498,14 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2487
2498
  let xShifted;
2488
2499
  // this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
2489
2500
  if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
2490
- transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
2501
+ transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
2491
2502
  position += 4;
2492
2503
  break;
2493
2504
  }
2494
2505
  }
2495
2506
  }
2496
2507
  }
2497
- transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8);
2508
+ transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
2498
2509
  targetView.setFloat64(position, number, true);
2499
2510
  position += 8;
2500
2511
  break;
@@ -2560,21 +2571,21 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2560
2571
  nextTransition.string8 = transition;
2561
2572
  pack(null, 0, true); // special call to notify that structures have been updated
2562
2573
  } else {
2563
- transition = createTypeTransition(nextTransition, UTF8, 1);
2574
+ transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
2564
2575
  }
2565
2576
  }
2566
2577
  } else if (refOffset === 0 && !usedAscii0) {
2567
2578
  usedAscii0 = true;
2568
- transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0);
2579
+ transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
2569
2580
  break; // don't increment position
2570
2581
  }// else ascii:
2571
2582
  else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
2572
- transition = createTypeTransition(nextTransition, ASCII, 1);
2583
+ transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
2573
2584
  target[position++] = refOffset;
2574
2585
  } else {
2575
2586
  // TODO: Enable ascii16 at some point, but get the logic right
2576
2587
  //if (isNotAscii)
2577
- transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2);
2588
+ transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
2578
2589
  //else
2579
2590
  //transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
2580
2591
  targetView.setUint16(position, refOffset, true);
@@ -2584,7 +2595,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2584
2595
  case 'object':
2585
2596
  if (value) {
2586
2597
  if (value.constructor === Date) {
2587
- transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8);
2598
+ transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
2588
2599
  targetView.setFloat64(position, value.getTime(), true);
2589
2600
  position += 8;
2590
2601
  } else {
@@ -2600,7 +2611,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2600
2611
  }
2601
2612
  break;
2602
2613
  case 'boolean':
2603
- transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1);
2614
+ transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
2604
2615
  target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
2605
2616
  break;
2606
2617
  case 'undefined':
@@ -2613,9 +2624,41 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2613
2624
  default:
2614
2625
  queuedReferences.push(key, value, keyIndex);
2615
2626
  }
2627
+ if (transition === undefined) return 0; // frozen: structure cap reached
2616
2628
  keyIndex++;
2617
2629
  }
2618
2630
 
2631
+ // Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
2632
+ // shared write position and we cannot cleanly bail afterward, so preflight the whole queued
2633
+ // chain through EXISTING transitions first: if the cap is reached and any field would need a
2634
+ // new structure, fall back to plain encoding now (return 0) — before touching the shared
2635
+ // position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
2636
+ // reading values above may have minted on this same instance since entry.
2637
+ if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
2638
+ let t = transition;
2639
+ for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
2640
+ // A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
2641
+ // msgpackr's write position. Its structure variant (object16 vs object32) depends on
2642
+ // the runtime ref-section offset (inline strings + earlier refs), which we can't know
2643
+ // before packing — and we can't bail after a pack without corrupting the fallback. So
2644
+ // under the cap, any record with a packing ref falls back to plain encoding now,
2645
+ // before any pack(). null/undefined refs don't pack, so they're walked normally.
2646
+ if (queuedReferences[i + 1] != null) return 0;
2647
+ const nt = t[queuedReferences[i]];
2648
+ if (!nt) return 0;
2649
+ const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
2650
+ if (!next) return 0;
2651
+ t = next;
2652
+ }
2653
+ if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
2654
+ }
2655
+
2656
+ // Past the preflight the chain is known, so no minting happens — except a rare offset
2657
+ // divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
2658
+ // the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
2659
+ // via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
2660
+ // record. packedRef keeps the record-id mint from bailing after a pack.
2661
+ let packedRef = false;
2619
2662
  for (let i = 0, l = queuedReferences.length; i < l;) {
2620
2663
  let key = queuedReferences[i++];
2621
2664
  let value = queuedReferences[i++];
@@ -2637,15 +2680,6 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2637
2680
  }
2638
2681
  let newPosition;
2639
2682
  if (value) {
2640
- /*if (typeof value === 'string') { // TODO: we could re-enable long strings
2641
- if (position + value.length * 3 > safeEnd) {
2642
- target = makeRoom(position + value.length * 3);
2643
- position -= start;
2644
- targetView = target.dataView;
2645
- start = 0;
2646
- }
2647
- newPosition = position + target.utf8Write(value, position, 0xffffffff);
2648
- } else { */
2649
2683
  let size;
2650
2684
  refOffset = refPosition - refsStartPosition;
2651
2685
  if (refOffset < 0xff00) {
@@ -2655,15 +2689,15 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2655
2689
  else if ((transition = nextTransition.object32))
2656
2690
  size = 4;
2657
2691
  else {
2658
- transition = createTypeTransition(nextTransition, OBJECT_DATA, 2);
2692
+ transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
2659
2693
  size = 2;
2660
2694
  }
2661
2695
  } else {
2662
- transition = nextTransition.object32 || createTypeTransition(nextTransition, OBJECT_DATA, 4);
2696
+ transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
2663
2697
  size = 4;
2664
2698
  }
2665
2699
  newPosition = pack(value, refPosition);
2666
- //}
2700
+ packedRef = true;
2667
2701
  if (typeof newPosition === 'object') {
2668
2702
  // re-allocated
2669
2703
  refPosition = newPosition.position;
@@ -2683,16 +2717,19 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2683
2717
  position += 4;
2684
2718
  }
2685
2719
  } else { // null or undefined
2686
- transition = nextTransition.object16 || createTypeTransition(nextTransition, OBJECT_DATA, 2);
2720
+ transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
2687
2721
  targetView.setInt16(position, value === null ? -10 : -9, true);
2688
2722
  position += 2;
2689
2723
  }
2690
2724
  keyIndex++;
2691
2725
  }
2692
2726
 
2693
-
2694
2727
  let recordId = transition[RECORD_SYMBOL];
2695
2728
  if (recordId == null) {
2729
+ // Flat records (no queued refs) reach here without packing, so the cap is enforced
2730
+ // cleanly. Records that packed nested refs already passed the preflight; either way
2731
+ // bailing now after refs were packed would corrupt the fallback.
2732
+ if (!packedRef && typedStructs.length >= cap) return 0;
2696
2733
  recordId = packr.typedStructs.length;
2697
2734
  let structure = [];
2698
2735
  let nextTransition = transition;
@@ -2746,7 +2783,11 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
2746
2783
  if (refsStartPosition === refPosition)
2747
2784
  return position; // no refs
2748
2785
  typedStructs.lastStringStart = position - start;
2749
- return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr);
2786
+ // Fixed section overflowed our estimate retry with the corrected size. The structure
2787
+ // is already minted at this point, so pass structureKnown=true to skip the cap check
2788
+ // (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
2789
+ // refs were already packed, and corrupt the fallback).
2790
+ return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
2750
2791
  }
2751
2792
  return refPosition;
2752
2793
  }
@@ -2778,9 +2819,36 @@ function anyType(transition, position, targetView, value) {
2778
2819
  // TODO: can we do an "any" type where we defer the decision?
2779
2820
  return;
2780
2821
  }
2781
- function createTypeTransition(transition, type, size) {
2822
+ // When the typed-structure dictionary reaches maxOwnStructures we stop minting new
2823
+ // structures/transitions. typedStructs is append-only and pinned on the long-lived
2824
+ // encoder (records reference structures by recordId), so an unbounded shape space —
2825
+ // e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
2826
+ // dictionary + transition trie without limit. `frozen` is passed in (derived from the
2827
+ // encoding instance's own typedStructs.length, never a shared global) so a re-entrant
2828
+ // encode on another instance can't flip it; while frozen, a missing transition returns
2829
+ // undefined so the caller bails and the record falls back to plain encoding.
2830
+ function createTypeTransition(transition, type, size, frozen) {
2782
2831
  let typeName = TYPE_NAMES[type] + (size << 3);
2783
- let newTransition = transition[typeName] || (transition[typeName] = Object.create(null));
2832
+ let newTransition = transition[typeName];
2833
+ if (newTransition) return newTransition;
2834
+ if (frozen) return undefined;
2835
+ newTransition = transition[typeName] = Object.create(null);
2836
+ newTransition.__type = type;
2837
+ newTransition.__size = size;
2838
+ newTransition.__parent = transition;
2839
+ return newTransition;
2840
+ }
2841
+
2842
+ // Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
2843
+ // already been pack()ed — at that point pack() has advanced msgpackr's shared write
2844
+ // position, so bailing with `return 0` would corrupt the fallback. We must finish the
2845
+ // encode instead, even if that means minting a (bounded) handful of structures past the
2846
+ // cap. The cap is still enforced up front via the preflight, before the first pack().
2847
+ function forceTypeTransition(transition, type, size) {
2848
+ let typeName = TYPE_NAMES[type] + (size << 3);
2849
+ let newTransition = transition[typeName];
2850
+ if (newTransition) return newTransition;
2851
+ newTransition = transition[typeName] = Object.create(null);
2784
2852
  newTransition.__type = type;
2785
2853
  newTransition.__size = size;
2786
2854
  newTransition.__parent = transition;
@@ -2814,7 +2882,8 @@ function onLoadedStructures(sharedData) {
2814
2882
  date64: null,
2815
2883
  };
2816
2884
  }
2817
- transition = createTypeTransition(nextTransition, type, size);
2885
+ // Replaying persisted structures is never subject to the cap — always mint.
2886
+ transition = createTypeTransition(nextTransition, type, size, false);
2818
2887
  }
2819
2888
  transition[RECORD_SYMBOL] = i;
2820
2889
  }
@@ -3172,6 +3241,7 @@ class UnpackrStream extends stream.Transform {
3172
3241
  options.objectMode = true;
3173
3242
  super(options);
3174
3243
  options.structures = [];
3244
+ this.maxIncompleteBufferSize = options.maxIncompleteBufferSize !== undefined ? options.maxIncompleteBufferSize : 0x4000000;
3175
3245
  this.unpackr = options.unpackr || new Unpackr(options);
3176
3246
  }
3177
3247
  _transform(chunk, encoding, callback) {
@@ -3184,19 +3254,23 @@ class UnpackrStream extends stream.Transform {
3184
3254
  values = this.unpackr.unpackMultiple(chunk);
3185
3255
  } catch(error) {
3186
3256
  if (error.incomplete) {
3187
- this.incompleteBuffer = chunk.slice(error.lastPosition);
3257
+ let incompleteBuffer = chunk.slice(error.lastPosition);
3258
+ if (incompleteBuffer.length > this.maxIncompleteBufferSize) {
3259
+ this.incompleteBuffer = null;
3260
+ return callback(new Error('Maximum incomplete buffer size exceeded'))
3261
+ }
3262
+ this.incompleteBuffer = incompleteBuffer;
3188
3263
  values = error.values;
3189
- }
3190
- else
3191
- throw error
3192
- } finally {
3193
- for (let value of values || []) {
3194
- if (value === null)
3195
- value = this.getNullValue();
3196
- this.push(value);
3264
+ } else {
3265
+ return callback(error)
3197
3266
  }
3198
3267
  }
3199
- if (callback) callback();
3268
+ for (let value of values || []) {
3269
+ if (value === null)
3270
+ value = this.getNullValue();
3271
+ this.push(value);
3272
+ }
3273
+ callback();
3200
3274
  }
3201
3275
  getNullValue() {
3202
3276
  return Symbol.for(null)