msgpackr 1.11.12 → 1.11.14
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/index-no-eval.cjs +8 -1
- package/dist/index-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.min.js +1 -1
- package/dist/index-no-eval.min.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/node.cjs +107 -31
- package/dist/node.cjs.map +1 -1
- package/dist/test.js +316 -31
- package/dist/test.js.map +1 -1
- package/pack.js +8 -1
- package/package.json +1 -1
- package/struct.js +99 -30
package/dist/node.cjs
CHANGED
|
@@ -1428,7 +1428,14 @@ class Packr extends Unpackr {
|
|
|
1428
1428
|
let newSharedData = prepareStructures$1(structures, packr);
|
|
1429
1429
|
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
|
|
1430
1430
|
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
|
|
1431
|
-
//
|
|
1431
|
+
// The save was declined (a concurrent writer updated the shared structures,
|
|
1432
|
+
// or the store transaction did not durably commit). Our in-memory
|
|
1433
|
+
// structures + transition trie may now reference record ids that were
|
|
1434
|
+
// never persisted; re-packing as-is would re-emit the same record pointing
|
|
1435
|
+
// at an unpersisted structure (-> "Record id is not defined" on decode).
|
|
1436
|
+
// Mark structures uninitialized so the re-pack reloads durable structures
|
|
1437
|
+
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
|
|
1438
|
+
structures.uninitialized = true;
|
|
1432
1439
|
return packr.pack(value, encodeOptions)
|
|
1433
1440
|
}
|
|
1434
1441
|
packr.lastNamedStructuresLength = sharedLength;
|
|
@@ -2406,9 +2413,16 @@ const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
|
|
|
2406
2413
|
return textEncoder.encodeInto(string, target.subarray(position)).written
|
|
2407
2414
|
} : false;
|
|
2408
2415
|
setWriteStructSlots(writeStruct, prepareStructures);
|
|
2409
|
-
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr) {
|
|
2416
|
+
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
|
|
2410
2417
|
let typedStructs = packr.typedStructs || (packr.typedStructs = []);
|
|
2411
2418
|
// note that we rely on pack.js to load stored structures before we get to this point
|
|
2419
|
+
// structureKnown is set only on the internal layout-retry below: attempt 1 already minted
|
|
2420
|
+
// this record's structure, so the retry re-encodes a known shape and must not re-apply the
|
|
2421
|
+
// cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
|
|
2422
|
+
// `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
|
|
2423
|
+
// re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
|
|
2424
|
+
const cap = packr.maxOwnStructures ?? Infinity;
|
|
2425
|
+
const frozen = !structureKnown && typedStructs.length >= cap;
|
|
2412
2426
|
let targetView = target.dataView;
|
|
2413
2427
|
let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
|
|
2414
2428
|
let safeEnd = target.length - 10;
|
|
@@ -2439,9 +2453,12 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2439
2453
|
let usedAscii0;
|
|
2440
2454
|
let keyIndex = 0;
|
|
2441
2455
|
for (let key in object) {
|
|
2442
|
-
let value = object[key];
|
|
2443
2456
|
let nextTransition = transition[key];
|
|
2457
|
+
// Resolve the key transition BEFORE reading the value: when frozen and the key is new we
|
|
2458
|
+
// bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
|
|
2459
|
+
// then again by the plain fallback (which would double-read a side-effecting accessor).
|
|
2444
2460
|
if (!nextTransition) {
|
|
2461
|
+
if (frozen) return 0;
|
|
2445
2462
|
transition[key] = nextTransition = {
|
|
2446
2463
|
key,
|
|
2447
2464
|
parent: transition,
|
|
@@ -2456,6 +2473,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2456
2473
|
date64: null
|
|
2457
2474
|
};
|
|
2458
2475
|
}
|
|
2476
|
+
let value = object[key];
|
|
2459
2477
|
if (position > safeEnd) {
|
|
2460
2478
|
target = makeRoom(position);
|
|
2461
2479
|
targetView = target.dataView;
|
|
@@ -2473,10 +2491,10 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2473
2491
|
if (nextId < 200 || !nextTransition.num64) {
|
|
2474
2492
|
if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
|
|
2475
2493
|
if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
|
|
2476
|
-
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1);
|
|
2494
|
+
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
|
|
2477
2495
|
target[position++] = number;
|
|
2478
2496
|
} else {
|
|
2479
|
-
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
|
|
2497
|
+
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
|
|
2480
2498
|
targetView.setUint32(position, number, true);
|
|
2481
2499
|
position += 4;
|
|
2482
2500
|
}
|
|
@@ -2487,14 +2505,14 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2487
2505
|
let xShifted;
|
|
2488
2506
|
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
|
|
2489
2507
|
if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
|
|
2490
|
-
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
|
|
2508
|
+
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
|
|
2491
2509
|
position += 4;
|
|
2492
2510
|
break;
|
|
2493
2511
|
}
|
|
2494
2512
|
}
|
|
2495
2513
|
}
|
|
2496
2514
|
}
|
|
2497
|
-
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8);
|
|
2515
|
+
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
|
|
2498
2516
|
targetView.setFloat64(position, number, true);
|
|
2499
2517
|
position += 8;
|
|
2500
2518
|
break;
|
|
@@ -2560,21 +2578,21 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2560
2578
|
nextTransition.string8 = transition;
|
|
2561
2579
|
pack(null, 0, true); // special call to notify that structures have been updated
|
|
2562
2580
|
} else {
|
|
2563
|
-
transition = createTypeTransition(nextTransition, UTF8, 1);
|
|
2581
|
+
transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
|
|
2564
2582
|
}
|
|
2565
2583
|
}
|
|
2566
2584
|
} else if (refOffset === 0 && !usedAscii0) {
|
|
2567
2585
|
usedAscii0 = true;
|
|
2568
|
-
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0);
|
|
2586
|
+
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
|
|
2569
2587
|
break; // don't increment position
|
|
2570
2588
|
}// else ascii:
|
|
2571
2589
|
else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
|
|
2572
|
-
transition = createTypeTransition(nextTransition, ASCII, 1);
|
|
2590
|
+
transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
|
|
2573
2591
|
target[position++] = refOffset;
|
|
2574
2592
|
} else {
|
|
2575
2593
|
// TODO: Enable ascii16 at some point, but get the logic right
|
|
2576
2594
|
//if (isNotAscii)
|
|
2577
|
-
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2);
|
|
2595
|
+
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
|
|
2578
2596
|
//else
|
|
2579
2597
|
//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
|
|
2580
2598
|
targetView.setUint16(position, refOffset, true);
|
|
@@ -2584,7 +2602,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2584
2602
|
case 'object':
|
|
2585
2603
|
if (value) {
|
|
2586
2604
|
if (value.constructor === Date) {
|
|
2587
|
-
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8);
|
|
2605
|
+
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
|
|
2588
2606
|
targetView.setFloat64(position, value.getTime(), true);
|
|
2589
2607
|
position += 8;
|
|
2590
2608
|
} else {
|
|
@@ -2600,7 +2618,7 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2600
2618
|
}
|
|
2601
2619
|
break;
|
|
2602
2620
|
case 'boolean':
|
|
2603
|
-
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1);
|
|
2621
|
+
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
|
|
2604
2622
|
target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
|
|
2605
2623
|
break;
|
|
2606
2624
|
case 'undefined':
|
|
@@ -2613,9 +2631,41 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2613
2631
|
default:
|
|
2614
2632
|
queuedReferences.push(key, value, keyIndex);
|
|
2615
2633
|
}
|
|
2634
|
+
if (transition === undefined) return 0; // frozen: structure cap reached
|
|
2616
2635
|
keyIndex++;
|
|
2617
2636
|
}
|
|
2618
2637
|
|
|
2638
|
+
// Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
|
|
2639
|
+
// shared write position and we cannot cleanly bail afterward, so preflight the whole queued
|
|
2640
|
+
// chain through EXISTING transitions first: if the cap is reached and any field would need a
|
|
2641
|
+
// new structure, fall back to plain encoding now (return 0) — before touching the shared
|
|
2642
|
+
// position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
|
|
2643
|
+
// reading values above may have minted on this same instance since entry.
|
|
2644
|
+
if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
|
|
2645
|
+
let t = transition;
|
|
2646
|
+
for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
|
|
2647
|
+
// A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
|
|
2648
|
+
// msgpackr's write position. Its structure variant (object16 vs object32) depends on
|
|
2649
|
+
// the runtime ref-section offset (inline strings + earlier refs), which we can't know
|
|
2650
|
+
// before packing — and we can't bail after a pack without corrupting the fallback. So
|
|
2651
|
+
// under the cap, any record with a packing ref falls back to plain encoding now,
|
|
2652
|
+
// before any pack(). null/undefined refs don't pack, so they're walked normally.
|
|
2653
|
+
if (queuedReferences[i + 1] != null) return 0;
|
|
2654
|
+
const nt = t[queuedReferences[i]];
|
|
2655
|
+
if (!nt) return 0;
|
|
2656
|
+
const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
|
|
2657
|
+
if (!next) return 0;
|
|
2658
|
+
t = next;
|
|
2659
|
+
}
|
|
2660
|
+
if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// Past the preflight the chain is known, so no minting happens — except a rare offset
|
|
2664
|
+
// divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
|
|
2665
|
+
// the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
|
|
2666
|
+
// via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
|
|
2667
|
+
// record. packedRef keeps the record-id mint from bailing after a pack.
|
|
2668
|
+
let packedRef = false;
|
|
2619
2669
|
for (let i = 0, l = queuedReferences.length; i < l;) {
|
|
2620
2670
|
let key = queuedReferences[i++];
|
|
2621
2671
|
let value = queuedReferences[i++];
|
|
@@ -2637,15 +2687,6 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2637
2687
|
}
|
|
2638
2688
|
let newPosition;
|
|
2639
2689
|
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
2690
|
let size;
|
|
2650
2691
|
refOffset = refPosition - refsStartPosition;
|
|
2651
2692
|
if (refOffset < 0xff00) {
|
|
@@ -2655,15 +2696,15 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2655
2696
|
else if ((transition = nextTransition.object32))
|
|
2656
2697
|
size = 4;
|
|
2657
2698
|
else {
|
|
2658
|
-
transition =
|
|
2699
|
+
transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
|
|
2659
2700
|
size = 2;
|
|
2660
2701
|
}
|
|
2661
2702
|
} else {
|
|
2662
|
-
transition = nextTransition.object32 ||
|
|
2703
|
+
transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
|
|
2663
2704
|
size = 4;
|
|
2664
2705
|
}
|
|
2665
2706
|
newPosition = pack(value, refPosition);
|
|
2666
|
-
|
|
2707
|
+
packedRef = true;
|
|
2667
2708
|
if (typeof newPosition === 'object') {
|
|
2668
2709
|
// re-allocated
|
|
2669
2710
|
refPosition = newPosition.position;
|
|
@@ -2683,16 +2724,19 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2683
2724
|
position += 4;
|
|
2684
2725
|
}
|
|
2685
2726
|
} else { // null or undefined
|
|
2686
|
-
transition = nextTransition.object16 ||
|
|
2727
|
+
transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
|
|
2687
2728
|
targetView.setInt16(position, value === null ? -10 : -9, true);
|
|
2688
2729
|
position += 2;
|
|
2689
2730
|
}
|
|
2690
2731
|
keyIndex++;
|
|
2691
2732
|
}
|
|
2692
2733
|
|
|
2693
|
-
|
|
2694
2734
|
let recordId = transition[RECORD_SYMBOL];
|
|
2695
2735
|
if (recordId == null) {
|
|
2736
|
+
// Flat records (no queued refs) reach here without packing, so the cap is enforced
|
|
2737
|
+
// cleanly. Records that packed nested refs already passed the preflight; either way
|
|
2738
|
+
// bailing now after refs were packed would corrupt the fallback.
|
|
2739
|
+
if (!packedRef && typedStructs.length >= cap) return 0;
|
|
2696
2740
|
recordId = packr.typedStructs.length;
|
|
2697
2741
|
let structure = [];
|
|
2698
2742
|
let nextTransition = transition;
|
|
@@ -2746,7 +2790,11 @@ function writeStruct(object, target, encodingStart, position, structures, makeRo
|
|
|
2746
2790
|
if (refsStartPosition === refPosition)
|
|
2747
2791
|
return position; // no refs
|
|
2748
2792
|
typedStructs.lastStringStart = position - start;
|
|
2749
|
-
|
|
2793
|
+
// Fixed section overflowed our estimate — retry with the corrected size. The structure
|
|
2794
|
+
// is already minted at this point, so pass structureKnown=true to skip the cap check
|
|
2795
|
+
// (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
|
|
2796
|
+
// refs were already packed, and corrupt the fallback).
|
|
2797
|
+
return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
|
|
2750
2798
|
}
|
|
2751
2799
|
return refPosition;
|
|
2752
2800
|
}
|
|
@@ -2778,9 +2826,36 @@ function anyType(transition, position, targetView, value) {
|
|
|
2778
2826
|
// TODO: can we do an "any" type where we defer the decision?
|
|
2779
2827
|
return;
|
|
2780
2828
|
}
|
|
2781
|
-
|
|
2829
|
+
// When the typed-structure dictionary reaches maxOwnStructures we stop minting new
|
|
2830
|
+
// structures/transitions. typedStructs is append-only and pinned on the long-lived
|
|
2831
|
+
// encoder (records reference structures by recordId), so an unbounded shape space —
|
|
2832
|
+
// e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
|
|
2833
|
+
// dictionary + transition trie without limit. `frozen` is passed in (derived from the
|
|
2834
|
+
// encoding instance's own typedStructs.length, never a shared global) so a re-entrant
|
|
2835
|
+
// encode on another instance can't flip it; while frozen, a missing transition returns
|
|
2836
|
+
// undefined so the caller bails and the record falls back to plain encoding.
|
|
2837
|
+
function createTypeTransition(transition, type, size, frozen) {
|
|
2838
|
+
let typeName = TYPE_NAMES[type] + (size << 3);
|
|
2839
|
+
let newTransition = transition[typeName];
|
|
2840
|
+
if (newTransition) return newTransition;
|
|
2841
|
+
if (frozen) return undefined;
|
|
2842
|
+
newTransition = transition[typeName] = Object.create(null);
|
|
2843
|
+
newTransition.__type = type;
|
|
2844
|
+
newTransition.__size = size;
|
|
2845
|
+
newTransition.__parent = transition;
|
|
2846
|
+
return newTransition;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
// Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
|
|
2850
|
+
// already been pack()ed — at that point pack() has advanced msgpackr's shared write
|
|
2851
|
+
// position, so bailing with `return 0` would corrupt the fallback. We must finish the
|
|
2852
|
+
// encode instead, even if that means minting a (bounded) handful of structures past the
|
|
2853
|
+
// cap. The cap is still enforced up front via the preflight, before the first pack().
|
|
2854
|
+
function forceTypeTransition(transition, type, size) {
|
|
2782
2855
|
let typeName = TYPE_NAMES[type] + (size << 3);
|
|
2783
|
-
let newTransition = transition[typeName]
|
|
2856
|
+
let newTransition = transition[typeName];
|
|
2857
|
+
if (newTransition) return newTransition;
|
|
2858
|
+
newTransition = transition[typeName] = Object.create(null);
|
|
2784
2859
|
newTransition.__type = type;
|
|
2785
2860
|
newTransition.__size = size;
|
|
2786
2861
|
newTransition.__parent = transition;
|
|
@@ -2814,7 +2889,8 @@ function onLoadedStructures(sharedData) {
|
|
|
2814
2889
|
date64: null,
|
|
2815
2890
|
};
|
|
2816
2891
|
}
|
|
2817
|
-
|
|
2892
|
+
// Replaying persisted structures is never subject to the cap — always mint.
|
|
2893
|
+
transition = createTypeTransition(nextTransition, type, size, false);
|
|
2818
2894
|
}
|
|
2819
2895
|
transition[RECORD_SYMBOL] = i;
|
|
2820
2896
|
}
|