msgpackr 1.6.2 → 1.7.0-alpha1

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/node-index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { Packr, Encoder, addExtension, pack, encode, NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } from './pack.js'
2
2
  export { Unpackr, Decoder, C1, unpack, unpackMultiple, decode, FLOAT32_OPTIONS, clearSource, roundFloat32, isNativeAccelerationEnabled } from './unpack.js'
3
+ import './struct.js'
3
4
  export { PackrStream, UnpackrStream, PackrStream as EncoderStream, UnpackrStream as DecoderStream } from './stream.js'
4
5
  export { decodeIter, encodeIter } from './iterators.js'
5
6
  export const useRecords = false
package/pack.js CHANGED
@@ -15,9 +15,10 @@ let targetView
15
15
  let position = 0
16
16
  let safeEnd
17
17
  let bundledStrings = null
18
+ let writeStructSlots
18
19
  const MAX_BUNDLE_SIZE = 0xf000
19
20
  const hasNonLatin = /[\u0080-\uFFFF]/
20
- const RECORD_SYMBOL = Symbol('record-id')
21
+ export const RECORD_SYMBOL = Symbol('record-id')
21
22
  export class Packr extends Unpackr {
22
23
  constructor(options) {
23
24
  super(options)
@@ -67,14 +68,14 @@ export class Packr extends Unpackr {
67
68
  this.pack = this.encode = function(value, encodeOptions) {
68
69
  if (!target) {
69
70
  target = new ByteArrayAllocate(8192)
70
- targetView = new DataView(target.buffer, 0, 8192)
71
+ targetView = target.dataView = new DataView(target.buffer, 0, 8192)
71
72
  position = 0
72
73
  }
73
74
  safeEnd = target.length - 10
74
75
  if (safeEnd - position < 0x800) {
75
76
  // don't start too close to the end,
76
77
  target = new ByteArrayAllocate(target.length)
77
- targetView = new DataView(target.buffer, 0, target.length)
78
+ targetView = target.dataView = new DataView(target.buffer, 0, target.length)
78
79
  safeEnd = target.length - 10
79
80
  position = 0
80
81
  } else
@@ -122,7 +123,10 @@ export class Packr extends Unpackr {
122
123
  if (hasSharedUpdate)
123
124
  hasSharedUpdate = false
124
125
  try {
125
- pack(value)
126
+ if (packr.randomAccessStructure)
127
+ writeStruct(value);
128
+ else
129
+ pack(value)
126
130
  if (bundledStrings) {
127
131
  writeBundles(start, pack)
128
132
  }
@@ -596,7 +600,7 @@ export class Packr extends Unpackr {
596
600
  } else // faster handling for smaller buffers
597
601
  newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12
598
602
  let newBuffer = new ByteArrayAllocate(newSize)
599
- targetView = new DataView(newBuffer.buffer, 0, newSize)
603
+ targetView = newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize)
600
604
  end = Math.min(end, target.length)
601
605
  if (target.copy)
602
606
  target.copy(newBuffer, 0, start, end)
@@ -687,6 +691,21 @@ export class Packr extends Unpackr {
687
691
  target[insertionOffset + start] = keysTarget[0]
688
692
  }
689
693
  }
694
+ const writeStruct = (object, safePrototype) => {
695
+ let newPosition = writeStructSlots(object, target, position, structures, makeRoom, (value, newPosition) => {
696
+ position = newPosition;
697
+ if (start > 0) {
698
+ pack(value);
699
+ if (start == 0)
700
+ return { position, targetView }; // indicate the buffer was re-allocated
701
+ } else
702
+ pack(value);
703
+ return position;
704
+ })
705
+ if (newPosition === 0) // bail and go to a msgpack object
706
+ return writeObject(object, true);
707
+ position = newPosition;
708
+ }
690
709
  }
691
710
  useBuffer(buffer) {
692
711
  // this means we are finished using our own buffer and we can write over it safely
@@ -923,6 +942,9 @@ export function addExtension(extension) {
923
942
  }
924
943
  unpackAddExtension(extension)
925
944
  }
945
+ export function setWriteStructSlots(func) {
946
+ writeStructSlots = func;
947
+ }
926
948
 
927
949
  let defaultPackr = new Packr({ useRecords: false })
928
950
  export const pack = defaultPackr.pack
package/package.json CHANGED
@@ -1,83 +1,83 @@
1
- {
2
- "name": "msgpackr",
3
- "author": "Kris Zyp",
4
- "version": "1.6.2",
5
- "description": "Ultra-fast MessagePack implementation with extensions for records and structured cloning",
6
- "license": "MIT",
7
- "types": "./index.d.ts",
8
- "main": "./dist/node.cjs",
9
- "module": "./index.js",
10
- "keywords": [
11
- "MessagePack",
12
- "msgpack",
13
- "performance",
14
- "structured",
15
- "clone"
16
- ],
17
- "repository": {
18
- "type": "git",
19
- "url": "http://github.com/kriszyp/msgpackr"
20
- },
21
- "scripts": {
22
- "benchmark": "node ./tests/benchmark.cjs",
23
- "build": "rollup -c",
24
- "dry-run": "npm publish --dry-run",
25
- "prepare": "npm run build",
26
- "test": "mocha tests/test**.*js -u tdd --experimental-json-modules"
27
- },
28
- "type": "module",
29
- "exports": {
30
- ".": {
31
- "node": {
32
- "require": "./dist/node.cjs",
33
- "import": "./node-index.js"
34
- },
35
- "bun": {
36
- "require": "./dist/node.cjs",
37
- "import": "./node-index.js"
38
- },
39
- "default": "./index.js"
40
- },
41
- "./pack": {
42
- "node": {
43
- "import": "./index.js",
44
- "require": "./dist/node.cjs"
45
- },
46
- "bun": {
47
- "import": "./index.js",
48
- "require": "./dist/node.cjs"
49
- },
50
- "default": "./pack.js"
51
- },
52
- "./unpack": {
53
- "node": {
54
- "import": "./index.js",
55
- "require": "./dist/node.cjs"
56
- },
57
- "bun": {
58
- "import": "./index.js",
59
- "require": "./dist/node.cjs"
60
- },
61
- "default": "./unpack.js"
62
- }
63
- },
64
- "files": [
65
- "/dist",
66
- "*.md",
67
- "/*.js",
68
- "/*.ts"
69
- ],
70
- "optionalDependencies": {
71
- "msgpackr-extract": "^2.0.2"
72
- },
73
- "devDependencies": {
74
- "@rollup/plugin-json": "^4.1.0",
75
- "@types/node": "latest",
76
- "async": "^3",
77
- "chai": "^4.3.4",
78
- "esm": "^3.2.25",
79
- "mocha": "^8.1.3",
80
- "rollup": "^1.20.3",
81
- "rollup-plugin-babel-minify": "^9.0.0"
82
- }
83
- }
1
+ {
2
+ "name": "msgpackr",
3
+ "author": "Kris Zyp",
4
+ "version": "1.7.0-alpha1",
5
+ "description": "Ultra-fast MessagePack implementation with extensions for records and structured cloning",
6
+ "license": "MIT",
7
+ "types": "./index.d.ts",
8
+ "main": "./dist/node.cjs",
9
+ "module": "./index.js",
10
+ "keywords": [
11
+ "MessagePack",
12
+ "msgpack",
13
+ "performance",
14
+ "structured",
15
+ "clone"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "http://github.com/kriszyp/msgpackr"
20
+ },
21
+ "scripts": {
22
+ "benchmark": "node ./tests/benchmark.cjs",
23
+ "build": "rollup -c",
24
+ "dry-run": "npm publish --dry-run",
25
+ "prepare": "npm run build",
26
+ "test": "mocha tests/test**.*js -u tdd --experimental-json-modules"
27
+ },
28
+ "type": "module",
29
+ "exports": {
30
+ ".": {
31
+ "node": {
32
+ "require": "./dist/node.cjs",
33
+ "import": "./node-index.js"
34
+ },
35
+ "bun": {
36
+ "require": "./dist/node.cjs",
37
+ "import": "./node-index.js"
38
+ },
39
+ "default": "./index.js"
40
+ },
41
+ "./pack": {
42
+ "node": {
43
+ "import": "./index.js",
44
+ "require": "./dist/node.cjs"
45
+ },
46
+ "bun": {
47
+ "import": "./index.js",
48
+ "require": "./dist/node.cjs"
49
+ },
50
+ "default": "./pack.js"
51
+ },
52
+ "./unpack": {
53
+ "node": {
54
+ "import": "./index.js",
55
+ "require": "./dist/node.cjs"
56
+ },
57
+ "bun": {
58
+ "import": "./index.js",
59
+ "require": "./dist/node.cjs"
60
+ },
61
+ "default": "./unpack.js"
62
+ }
63
+ },
64
+ "files": [
65
+ "/dist",
66
+ "*.md",
67
+ "/*.js",
68
+ "/*.ts"
69
+ ],
70
+ "optionalDependencies": {
71
+ "msgpackr-extract": "^2.1.1"
72
+ },
73
+ "devDependencies": {
74
+ "@rollup/plugin-json": "^4.1.0",
75
+ "@types/node": "latest",
76
+ "async": "^3",
77
+ "chai": "^4.3.4",
78
+ "esm": "^3.2.25",
79
+ "mocha": "^8.1.3",
80
+ "rollup": "^1.20.3",
81
+ "rollup-plugin-babel-minify": "^9.0.0"
82
+ }
83
+ }
package/struct.js ADDED
@@ -0,0 +1,260 @@
1
+ // first four bits
2
+ // 0000 - unsigned int
3
+ // 0010 - float32
4
+ // 0011 - float32
5
+ // 0100 - float32
6
+ // 0101 - float32
7
+ // 0110 - latin string reference
8
+ // 0111 - plain reference
9
+ // 1000 - structure reference
10
+ // 1001 - random access structure reference
11
+ // 1010 - float32
12
+ // 1011 - float32
13
+ // 1100 - float32
14
+ // 1101 - float32
15
+ // 1110 - constants and 3-byte strings
16
+ // 1111 - negative int
17
+
18
+ // first three bits
19
+ // 000 - unsigned int
20
+ // 001 - float32
21
+ // 010 - float32
22
+ // 011 - latin string reference
23
+ // 100 - reference
24
+ // 101 - float32
25
+ // 110 - float32
26
+ // 111 - constants and 3-byte strings
27
+
28
+ import { setWriteStructSlots, RECORD_SYMBOL } from './pack.js'
29
+ import { setReadStruct, unpack, mult10 } from './unpack.js';
30
+ const hasNonLatin = /[\u0080-\uFFFF]/;
31
+ const float32Headers = [false, true, true, false, false, true, true, false]
32
+ setWriteStructSlots(writeStruct);
33
+ function writeStruct(object, target, position, structures, makeRoom, pack) {
34
+ let transition = structures.transitions || false
35
+ let newTransitions = 0
36
+ let keyCount = 0;
37
+ let start = position;
38
+ position += 4;
39
+ let queuedReferences = [];
40
+ let uint32 = target.uint32 || (target.uint32 = new Uint32Array(target.buffer));
41
+ let targetView = target.dataView;
42
+ let encoded;
43
+ let stringData = '';
44
+ let safeEnd = target.length - 10;
45
+ for (let key in object) {
46
+ let nextTransition = transition[key]
47
+ if (!nextTransition) {
48
+ return 0; // bail
49
+ //nextTransition = transition[key] = Object.create(null)
50
+ //newTransitions++
51
+ }
52
+ if (position > safeEnd) {
53
+ let newPosition = position - start;
54
+ target = makeRoom(position)
55
+ position = newPosition;
56
+ start = 0
57
+ safeEnd = target.length - 10
58
+ }
59
+ transition = nextTransition
60
+ let value = object[key];
61
+ switch (typeof value) {
62
+ case 'number':
63
+ if (value >>> 0 === value && value < 0x20000000) {
64
+ encoded = value;
65
+ break;
66
+ } else if (value < 0x100000000 && value >= -0x80000000) {
67
+ targetView.setFloat32(position, value, true)
68
+ if (float32Headers[target[position + 3] >>> 5]) {
69
+ let xShifted
70
+ // this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
71
+ if (((xShifted = value * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
72
+ position += 4;
73
+ continue;
74
+ }
75
+ }
76
+ }
77
+ // fall back to msgpack encoding
78
+ queuedReferences.push(value, position - start);
79
+ position += 4;
80
+ continue;
81
+ case 'string':
82
+ if (hasNonLatin.test(value)) {
83
+ queuedReferences.push(value, position - start);
84
+ position += 4;
85
+ continue;
86
+ }
87
+ if (value.length < 4) { // we can inline really small strings
88
+ encoded = 0xf8000000 + (value.length << 24) + (value.charCodeAt(0) << 16) + (value.charCodeAt(1) << 8) + (value.charCodeAt(2) || 0)
89
+ // TODO: determining remaining and make max value be a ratio of that (probably 1/256th)
90
+ } else if (value.length < 256 && stringData.length < 61440) {
91
+ // bundle these strings
92
+ encoded = 0x60000000 | (value.length << 16) | stringData.length;
93
+ stringData += value;
94
+ } else { // else queue it
95
+ queuedReferences.push(value, position - start);
96
+ position += 4;
97
+ continue;
98
+ }
99
+ break;
100
+ case 'object':
101
+ if (value) {
102
+ queuedReferences.push(value, position - start);
103
+ position += 4;
104
+ continue;
105
+ } else { // null
106
+ encoded = 0xe0000000;
107
+ }
108
+ break;
109
+ case 'boolean':
110
+ encoded = value ? 0xe3000000 : 0xe2000000;
111
+ break;
112
+ case 'undefined':
113
+ encoded = 0xe1000000;
114
+ break;
115
+ }
116
+ targetView.setUint32(position, encoded, true);
117
+ position += 4;
118
+ }
119
+ let recordId = transition[RECORD_SYMBOL]
120
+ if (!(recordId < 1024)) {
121
+ // for now just punt and go back to writeObject
122
+ return 0;
123
+ // newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions, true)
124
+ }
125
+ let stringLength = stringData.length;
126
+ if (stringData) {
127
+ if (position + stringLength > safeEnd) {
128
+ target = makeRoom(position + stringLength);
129
+ }
130
+ position += target.latin1Write(stringData, position, 0xffffffff);
131
+ }
132
+ target[start] = recordId >> 8;
133
+ target[start + 1] = recordId & 0xff;
134
+ target[start + 2] = stringLength >> 8;
135
+ target[start + 3] = stringLength & 0xff;
136
+ let queued32BitReferences;
137
+ for (let i = 0, l = queuedReferences.length; i < l;) {
138
+ let value = queuedReferences[i++];
139
+ let slotOffset = queuedReferences[i++] + start;
140
+ let offset = position - slotOffset;
141
+ if (offset < 0x1f000000) {
142
+ targetView.setUint32(slotOffset, 0x80000000 | (offset), true);
143
+ } else {
144
+ if (!queued32BitReferences)
145
+ queued32BitReferences = [];
146
+ queued32BitReferences.push({slotOffset, offset: position - start});
147
+ }
148
+ let newPosition = pack(value, position);
149
+ if (typeof newPosition === 'object') {
150
+ // re-allocated
151
+ position = newPosition.position;
152
+ targetView = newPosition.targetView;
153
+ start = 0;
154
+ } else
155
+ position = newPosition;
156
+ }
157
+ if (queued32BitReferences) {
158
+ // TODO: makeRoom
159
+ for (let i = 0, l = queued32BitReferences.length; i < l; i++) {
160
+ let ref = queued32BitReferences[i];
161
+ targetView.setUint32(ref.slotOffset, 0xa0000000 - ((l - i) << 2), true);
162
+ targetView.setUint32(position, ref.offset, true);
163
+ position += 4;
164
+ }
165
+ }
166
+
167
+ return position;
168
+ }
169
+ var sourceSymbol = Symbol('source')
170
+ function readStruct(src, position, srcEnd, structure, unpackr) {
171
+ var stringLength = (src[position++] << 8) | src[position++];
172
+ var construct = structure.construct;
173
+ var srcString;
174
+ if (!construct) {
175
+ construct = structure.construct = function() {
176
+ }
177
+ var prototype = construct.prototype;
178
+ Object.defineProperty(prototype, 'toJSON', {
179
+ get() {
180
+ // return an enumerable object with own properties to JSON stringify
181
+ let resolved = {};
182
+ for (let i = 0, l = structure.length; i < l; i++) {
183
+ let key = structure[i];
184
+ resolved[key] = this[key];
185
+ }
186
+ return resolved;
187
+ },
188
+ // not enumerable or anything
189
+ });
190
+ for (let i = 0, l = structure.length; i < l; i++) {
191
+ let key = structure[i];
192
+ Object.defineProperty(prototype, key, {
193
+ get() {
194
+ let source = this[sourceSymbol];
195
+ let src = source.src;
196
+ //let uint32 = src.uint32 || (src.uint32 = new Uint32Array(src.buffer, src.byteOffset, src.byteLength));
197
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
198
+ let position = source.position + (i << 2);
199
+ let value = dataView.getUint32(position, true);
200
+ let start;
201
+ switch (value >>> 29) {
202
+ case 0:
203
+ return value;
204
+ case 3:
205
+ if (value & 0x10000000) {
206
+ start = (value & 0xffff) + position;
207
+ return src.toString('utf8', start, start + ((value >> 16) & 0x7ff));
208
+ } else {
209
+ if (!srcString) {
210
+ start = source.position + (l << 2);
211
+ srcString = src.toString('latin1', start, start + stringLength);
212
+ }
213
+ start = value & 0xffff;
214
+ return srcString.slice(start, start + ((value >> 16) & 0x7ff));
215
+ }
216
+ case 4:
217
+ start = (0x1fffffff & value) + position;
218
+ let end = srcEnd;
219
+ for (let next = i + 1; next < l; next++) {
220
+ position = source.position + (next << 2);
221
+ let nextValue = dataView.getUint32(position, true);;
222
+ if ((nextValue & 0xe0000000) == -0x80000000) {
223
+ end = (0x1fffffff & nextValue) + position;
224
+ break;
225
+ }
226
+ }
227
+ return unpackr.unpack(src.slice(start, end));
228
+ case 1: case 2: case 5: case 6:
229
+ let fValue = dataView.getFloat32(position, true);
230
+ // this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
231
+ let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
232
+ return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
233
+ case 7:
234
+ switch((value >> 24) & 0x1f) {
235
+ case 0: return null;
236
+ case 1: return undefined;
237
+ case 2: return false;
238
+ case 3: return true;
239
+ case 8: return dataView.getFloat64(position + (value & 0x3ffffff), true);
240
+ case 0x18: return '';
241
+ case 0x19: return String.fromCharCode((value >> 16) & 0xff);
242
+ case 0x20: return String.fromCharCode((value >> 16) & 0xff, (value >> 8) & 0xff);
243
+ case 0x21: return String.fromCharCode((value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff);
244
+ default: throw new Error('Unknown constant');
245
+ }
246
+ }
247
+ },
248
+ enumerable: true,
249
+ });
250
+ }
251
+ }
252
+ var instance = new construct();
253
+ instance[sourceSymbol] = {
254
+ src,
255
+ uint32: src.uint32,
256
+ position,
257
+ }
258
+ return instance;
259
+ }
260
+ setReadStruct(readStruct)
package/unpack.js CHANGED
@@ -28,6 +28,7 @@ export const C1 = new C1Type()
28
28
  C1.name = 'MessagePack 0xC1'
29
29
  var sequentialMode = false
30
30
  var inlineObjectReadThreshold = 2
31
+ var readStruct
31
32
  try {
32
33
  new Function('')
33
34
  } catch(error) {
@@ -170,7 +171,13 @@ export function checkedRead() {
170
171
  if (sharedLength < currentStructures.length)
171
172
  currentStructures.length = sharedLength
172
173
  }
173
- let result = read()
174
+ let result
175
+ if (currentUnpackr.randomAccessStructure && src[position] < 0x40 && readStruct) {
176
+ let id = (src[position++] << 8) + src[position++];
177
+ result = readStruct(src, position, srcEnd, currentStructures[id - 0x40] || loadStructures()[id - 0x40], currentUnpackr)
178
+ position = srcEnd
179
+ } else
180
+ result = read()
174
181
  if (bundledStrings) // bundled strings to skip past
175
182
  position = bundledStrings.postBundlePosition
176
183
 
@@ -247,6 +254,8 @@ export function read() {
247
254
  for (let i = 0; i < token; i++) {
248
255
  array[i] = read()
249
256
  }
257
+ if (currentUnpackr.freezeData)
258
+ return Object.freeze(array)
250
259
  return array
251
260
  }
252
261
  } else if (token < 0xc0) {
@@ -457,7 +466,8 @@ function createStructureReader(structure, firstId) {
457
466
  function readObject() {
458
467
  // This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
459
468
  if (readObject.count++ > inlineObjectReadThreshold) {
460
- let readObject = structure.read = (new Function('r', 'return function(){return {' + structure.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}}'))(read)
469
+ let readObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
470
+ '({' + structure.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read)
461
471
  if (structure.highByte === 0)
462
472
  structure.read = createSecondByteReader(firstId, structure.read)
463
473
  return readObject() // second byte is already read, if there is one so immediately read object
@@ -467,6 +477,8 @@ function createStructureReader(structure, firstId) {
467
477
  let key = structure[i]
468
478
  object[key] = read()
469
479
  }
480
+ if (currentUnpackr.freezeData)
481
+ return Object.freeze(object);
470
482
  return object
471
483
  }
472
484
  readObject.count = 0
@@ -604,6 +616,8 @@ function readArray(length) {
604
616
  for (let i = 0; i < length; i++) {
605
617
  array[i] = read()
606
618
  }
619
+ if (currentUnpackr.freezeData)
620
+ return Object.freeze(array)
607
621
  return array
608
622
  }
609
623
 
@@ -1059,3 +1073,6 @@ export function roundFloat32(float32Number) {
1059
1073
  let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]
1060
1074
  return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
1061
1075
  }
1076
+ export function setReadStruct(func) {
1077
+ readStruct = func;
1078
+ }