msgpackr 1.11.14 → 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/test.js CHANGED
@@ -1357,7 +1357,13 @@
1357
1357
  hasSharedUpdate = false;
1358
1358
  let encodingError;
1359
1359
  try {
1360
- if (packr.randomAccessStructure && value && typeof value === 'object') {
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)
@@ -4849,5 +4855,74 @@
4849
4855
  });
4850
4856
  });
4851
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
+
4852
4927
  })(chai, null, module, fs);
4853
4928
  //# sourceMappingURL=test.js.map