msgpackr 1.11.13 → 1.12.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/dist/index-no-eval.cjs +15 -2
- 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 +15 -2
- 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 +15 -2
- package/dist/node.cjs.map +1 -1
- package/dist/test.js +109 -2
- package/dist/test.js.map +1 -1
- package/pack.js +15 -2
- package/package.json +1 -1
package/dist/test.js
CHANGED
|
@@ -1357,7 +1357,13 @@
|
|
|
1357
1357
|
hasSharedUpdate = false;
|
|
1358
1358
|
let encodingError;
|
|
1359
1359
|
try {
|
|
1360
|
-
|
|
1360
|
+
// readOnlyStructures: skip the random-access struct write path so NO new struct is
|
|
1361
|
+
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
|
|
1362
|
+
// integer boundary are preserved, so existing struct data still decodes), but objects
|
|
1363
|
+
// fall through to the normal pack()->writeObject->writeRecord path and are written as
|
|
1364
|
+
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
|
|
1365
|
+
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
|
|
1366
|
+
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
|
|
1361
1367
|
if (value.constructor === Object) writeStruct(value); // simple object
|
|
1362
1368
|
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
|
|
1363
1369
|
// allow user classes, if they don't need special handling (but do use toJSON if available)
|
|
@@ -1423,7 +1429,14 @@
|
|
|
1423
1429
|
let newSharedData = prepareStructures$1(structures, packr);
|
|
1424
1430
|
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
|
|
1425
1431
|
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
|
|
1426
|
-
//
|
|
1432
|
+
// The save was declined (a concurrent writer updated the shared structures,
|
|
1433
|
+
// or the store transaction did not durably commit). Our in-memory
|
|
1434
|
+
// structures + transition trie may now reference record ids that were
|
|
1435
|
+
// never persisted; re-packing as-is would re-emit the same record pointing
|
|
1436
|
+
// at an unpersisted structure (-> "Record id is not defined" on decode).
|
|
1437
|
+
// Mark structures uninitialized so the re-pack reloads durable structures
|
|
1438
|
+
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
|
|
1439
|
+
structures.uninitialized = true;
|
|
1427
1440
|
return packr.pack(value, encodeOptions)
|
|
1428
1441
|
}
|
|
1429
1442
|
packr.lastNamedStructuresLength = sharedLength;
|
|
@@ -4219,6 +4232,31 @@
|
|
|
4219
4232
|
assert.deepEqual(inputData, outputData);
|
|
4220
4233
|
});
|
|
4221
4234
|
|
|
4235
|
+
test('declined structure save re-packs against durable structures (no dangling record)', function() {
|
|
4236
|
+
// Regression: when saveStructures declines a save (a concurrent writer updated the shared
|
|
4237
|
+
// structures, or the store txn did not durably commit), the in-memory structures/transition
|
|
4238
|
+
// trie reference a record id that was never persisted. The re-pack must reload the durable
|
|
4239
|
+
// structures and re-mint/re-save — otherwise it re-emits the same record pointing at an
|
|
4240
|
+
// unsaved structure, and reads throw "Record id is not defined".
|
|
4241
|
+
const meta = new Packr();
|
|
4242
|
+
let store = null;
|
|
4243
|
+
let declinedOnce = false;
|
|
4244
|
+
const packr = new Packr({
|
|
4245
|
+
useRecords: true,
|
|
4246
|
+
getStructures() { return store ? meta.unpack(store) : undefined },
|
|
4247
|
+
saveStructures(structures) {
|
|
4248
|
+
if (!declinedOnce) { declinedOnce = true; return false } // decline the first save
|
|
4249
|
+
store = meta.pack(structures); return true
|
|
4250
|
+
},
|
|
4251
|
+
});
|
|
4252
|
+
const a = packr.pack({ x: 9, y: 8 }); // first mint is declined -> must re-pack + re-save
|
|
4253
|
+
const b = packr.pack({ x: 7, y: 6 }); // same shape -> must still reference a saved structure
|
|
4254
|
+
// A fresh reader sees only the durably-saved structures (another thread / post-restart):
|
|
4255
|
+
const reader = new Packr({ getStructures() { return store ? meta.unpack(store) : undefined } });
|
|
4256
|
+
assert.deepEqual(reader.unpack(a), { x: 9, y: 8 });
|
|
4257
|
+
assert.deepEqual(reader.unpack(b), { x: 7, y: 6 });
|
|
4258
|
+
});
|
|
4259
|
+
|
|
4222
4260
|
test('big buffer', function() {
|
|
4223
4261
|
var size = 100000000;
|
|
4224
4262
|
var data = new Uint8Array(size).fill(1);
|
|
@@ -4817,5 +4855,74 @@
|
|
|
4817
4855
|
});
|
|
4818
4856
|
});
|
|
4819
4857
|
|
|
4858
|
+
suite('msgpackr – readOnlyStructures (write-disable, read-compatible)', function () {
|
|
4859
|
+
// readOnlyStructures keeps randomAccessStructure decode semantics — existing random-access
|
|
4860
|
+
// struct data still decodes and the struct-safe integer boundary is preserved — but never mints
|
|
4861
|
+
// a new random-access structure on write. Objects fall through to the classic shared-structure
|
|
4862
|
+
// record path (bytes 0x40-0x7f, disjoint from struct headers at 0x20-0x3f), the bounded,
|
|
4863
|
+
// width-agnostic encoding used before struct mode. This is the mechanism for disabling typed
|
|
4864
|
+
// structures on an existing store without breaking reads.
|
|
4865
|
+
const sharedStore = () => {
|
|
4866
|
+
let saved;
|
|
4867
|
+
return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
|
|
4868
|
+
};
|
|
4869
|
+
|
|
4870
|
+
test('decodes pre-existing random-access structs (read stays enabled)', function () {
|
|
4871
|
+
const full = new Packr({ randomAccessStructure: true, useRecords: true });
|
|
4872
|
+
const rec = { a: 1, b: 'hello', c: true, n: 42 };
|
|
4873
|
+
const buf = full.pack(rec);
|
|
4874
|
+
assert.ok(buf[0] >= 0x20 && buf[0] < 0x40, 'control: full encoder emits a random-access struct');
|
|
4875
|
+
|
|
4876
|
+
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true });
|
|
4877
|
+
ro.typedStructs = full.typedStructs; // share the existing random-access dictionary
|
|
4878
|
+
assert.deepEqual(ro.unpack(buf), rec);
|
|
4879
|
+
});
|
|
4880
|
+
|
|
4881
|
+
test('writes classic shared-structure records (0x40-0x7f), never a random-access struct', function () {
|
|
4882
|
+
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|
4883
|
+
const rec = { x: 9, y: 'world', z: [1, 2, 3], nested: { deep: 50, k: 42 } };
|
|
4884
|
+
const b1 = ro.pack(rec);
|
|
4885
|
+
assert.ok(b1[0] >= 0x40 && b1[0] < 0x80, 'expected a classic record (0x40-0x7f), got 0x' + b1[0].toString(16));
|
|
4886
|
+
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted');
|
|
4887
|
+
assert.deepEqual(ro.unpack(b1), rec);
|
|
4888
|
+
// a second record of the same shape reuses the shared classic structure (still classic)
|
|
4889
|
+
const b2 = ro.pack({ x: 1, y: 'a', z: [], nested: { deep: 0, k: 0 } });
|
|
4890
|
+
assert.ok(b2[0] >= 0x40 && b2[0] < 0x80, 'second record also classic');
|
|
4891
|
+
});
|
|
4892
|
+
|
|
4893
|
+
test('preserves the struct-safe integer boundary (nested 0x20-0x3f ints do not collide)', function () {
|
|
4894
|
+
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|
4895
|
+
for (const v of [0x1f, 0x20, 0x2a, 0x3f, 0x40, 0x7f]) {
|
|
4896
|
+
assert.deepEqual(ro.unpack(ro.pack({ v, nested: { w: v } })), { v, nested: { w: v } });
|
|
4897
|
+
}
|
|
4898
|
+
});
|
|
4899
|
+
|
|
4900
|
+
test('a normal randomAccessStructure reader decodes readOnly classic records (replication compat)', function () {
|
|
4901
|
+
let saved;
|
|
4902
|
+
const store = { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
|
|
4903
|
+
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...store });
|
|
4904
|
+
const roBuf = ro.pack({ p: 'q', n: 42 });
|
|
4905
|
+
assert.ok(roBuf[0] >= 0x40 && roBuf[0] < 0x80, 'readOnly writes a classic record');
|
|
4906
|
+
|
|
4907
|
+
// A peer that is NOT readOnly (a normal struct-writing node) shares the classic-structure
|
|
4908
|
+
// store and decodes the classic record correctly — no mapsAsObjects needed, since classic
|
|
4909
|
+
// records decode to objects natively.
|
|
4910
|
+
const peer = new Packr({ randomAccessStructure: true, useRecords: true, ...store });
|
|
4911
|
+
assert.deepEqual(peer.unpack(roBuf), { p: 'q', n: 42 });
|
|
4912
|
+
});
|
|
4913
|
+
|
|
4914
|
+
test('every object shape uses the classic path (null-proto, shadowed ctor) — no random-access struct', function () {
|
|
4915
|
+
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|
4916
|
+
|
|
4917
|
+
const np = Object.create(null); np.a = 1; np.b = 'x';
|
|
4918
|
+
assert.deepEqual(ro.unpack(ro.pack(np)), { a: 1, b: 'x' });
|
|
4919
|
+
|
|
4920
|
+
const cs = { constructor: 'shadow', a: 1 };
|
|
4921
|
+
assert.deepEqual(ro.unpack(ro.pack(cs)), { constructor: 'shadow', a: 1 });
|
|
4922
|
+
|
|
4923
|
+
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted for any shape');
|
|
4924
|
+
});
|
|
4925
|
+
});
|
|
4926
|
+
|
|
4820
4927
|
})(chai, null, module, fs);
|
|
4821
4928
|
//# sourceMappingURL=test.js.map
|