| (function (chai, stream, module, fs) { | |
| 'use strict'; | |
| var decoder; | |
| try { | |
| decoder = new TextDecoder(); | |
| } catch(error) {} | |
| var src; | |
| var srcEnd; | |
| var position$1 = 0; | |
| const EMPTY_ARRAY = []; | |
| var strings = EMPTY_ARRAY; | |
| var stringPosition = 0; | |
| var currentUnpackr = {}; | |
| var currentStructures; | |
| var srcString; | |
| var srcStringStart = 0; | |
| var srcStringEnd = 0; | |
| var bundledStrings$1; | |
| var referenceMap; | |
| var currentExtensions = []; | |
| var dataView; | |
| var defaultOptions = { | |
| useRecords: false, | |
| mapsAsObjects: true | |
| }; | |
| class C1Type {} | |
| const C1 = new C1Type(); | |
| C1.name = 'MessagePack 0xC1'; | |
| var sequentialMode = false; | |
| var inlineObjectReadThreshold = 2; | |
| let Unpackr$1 = class Unpackr { | |
| constructor(options) { | |
| if (options) { | |
| if (options.useRecords === false && options.mapsAsObjects === undefined) | |
| options.mapsAsObjects = true; | |
| if (options.sequential && options.trusted !== false) { | |
| options.trusted = true; | |
| if (!options.structures && options.useRecords != false) { | |
| options.structures = []; | |
| if (!options.maxSharedStructures) | |
| options.maxSharedStructures = 0; | |
| } | |
| } | |
| if (options.structures) | |
| options.structures.sharedLength = options.structures.length; | |
| else if (options.getStructures) { | |
| (options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures | |
| options.structures.sharedLength = 0; | |
| } | |
| if (options.int64AsNumber) { | |
| options.int64AsType = 'number'; | |
| } | |
| } | |
| Object.assign(this, options); | |
| } | |
| unpack(source, options) { | |
| if (src) { | |
| // re-entrant execution, save the state and restore it after we do this unpack | |
| return saveState(() => { | |
| clearSource(); | |
| return this ? this.unpack(source, options) : Unpackr$1.prototype.unpack.call(defaultOptions, source, options); | |
| }); | |
| } | |
| if (!source.buffer && source.constructor === ArrayBuffer) | |
| source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source); | |
| if (typeof options === 'object') { | |
| srcEnd = options.end || source.length; | |
| position$1 = options.start || 0; | |
| } else { | |
| position$1 = 0; | |
| srcEnd = options > -1 ? options : source.length; | |
| } | |
| stringPosition = 0; | |
| srcStringEnd = 0; | |
| srcString = null; | |
| strings = EMPTY_ARRAY; | |
| bundledStrings$1 = null; | |
| src = source; | |
| // this provides cached access to the data view for a buffer if it is getting reused, which is a recommend | |
| // technique for getting data from a database where it can be copied into an existing buffer instead of creating | |
| // new ones | |
| try { | |
| dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength)); | |
| } catch(error) { | |
| // if it doesn't have a buffer, maybe it is the wrong type of object | |
| src = null; | |
| if (source instanceof Uint8Array) | |
| throw error; | |
| throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source)); | |
| } | |
| if (this instanceof Unpackr$1) { | |
| currentUnpackr = this; | |
| if (this.structures) { | |
| currentStructures = this.structures; | |
| return checkedRead(options); | |
| } else if (!currentStructures || currentStructures.length > 0) { | |
| currentStructures = []; | |
| } | |
| } else { | |
| currentUnpackr = defaultOptions; | |
| if (!currentStructures || currentStructures.length > 0) | |
| currentStructures = []; | |
| } | |
| return checkedRead(options); | |
| } | |
| unpackMultiple(source, forEach) { | |
| let values, lastPosition = 0; | |
| try { | |
| sequentialMode = true; | |
| let size = source.length; | |
| let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size); | |
| if (forEach) { | |
| if (forEach(value, lastPosition, position$1) === false) return; | |
| while(position$1 < size) { | |
| lastPosition = position$1; | |
| if (forEach(checkedRead(), lastPosition, position$1) === false) { | |
| return; | |
| } | |
| } | |
| } | |
| else { | |
| values = [ value ]; | |
| while(position$1 < size) { | |
| lastPosition = position$1; | |
| values.push(checkedRead()); | |
| } | |
| return values; | |
| } | |
| } catch(error) { | |
| error.lastPosition = lastPosition; | |
| error.values = values; | |
| throw error; | |
| } finally { | |
| sequentialMode = false; | |
| clearSource(); | |
| } | |
| } | |
| _mergeStructures(loadedStructures, existingStructures) { | |
| if (this._onLoadedStructures) | |
| loadedStructures = this._onLoadedStructures(loadedStructures); | |
| loadedStructures = loadedStructures || []; | |
| if (Object.isFrozen(loadedStructures)) | |
| loadedStructures = loadedStructures.map(structure => structure.slice(0)); | |
| for (let i = 0, l = loadedStructures.length; i < l; i++) { | |
| let structure = loadedStructures[i]; | |
| if (structure) { | |
| structure.isShared = true; | |
| if (i >= 32) | |
| structure.highByte = (i - 32) >> 5; | |
| } | |
| } | |
| loadedStructures.sharedLength = loadedStructures.length; | |
| for (let id in existingStructures || []) { | |
| if (id >= 0) { | |
| let structure = loadedStructures[id]; | |
| let existing = existingStructures[id]; | |
| if (existing) { | |
| if (structure) | |
| (loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure; | |
| loadedStructures[id] = existing; | |
| } | |
| } | |
| } | |
| return this.structures = loadedStructures; | |
| } | |
| decode(source, options) { | |
| return this.unpack(source, options); | |
| } | |
| }; | |
| function checkedRead(options) { | |
| try { | |
| if (!currentUnpackr.trusted && !sequentialMode) { | |
| let sharedLength = currentStructures.sharedLength || 0; | |
| if (sharedLength < currentStructures.length) | |
| currentStructures.length = sharedLength; | |
| } | |
| let result; | |
| if (currentUnpackr._readStruct && src[position$1] < 0x40 && src[position$1] >= 0x20) { | |
| result = currentUnpackr._readStruct(src, position$1, srcEnd); | |
| src = null; // dispose of this so that recursive unpack calls don't save state | |
| if (!(options && options.lazy) && result) | |
| result = result.toJSON(); | |
| position$1 = srcEnd; | |
| } else | |
| result = read(); | |
| if (bundledStrings$1) { // bundled strings to skip past | |
| position$1 = bundledStrings$1.postBundlePosition; | |
| bundledStrings$1 = null; | |
| } | |
| if (sequentialMode) | |
| // we only need to restore the structures if there was an error, but if we completed a read, | |
| // we can clear this out and keep the structures we read | |
| currentStructures.restoreStructures = null; | |
| if (position$1 == srcEnd) { | |
| // finished reading this source, cleanup references | |
| if (currentStructures && currentStructures.restoreStructures) | |
| restoreStructures(); | |
| currentStructures = null; | |
| src = null; | |
| if (referenceMap) | |
| referenceMap = null; | |
| } else if (position$1 > srcEnd) { | |
| // over read | |
| throw new Error('Unexpected end of MessagePack data'); | |
| } else if (!sequentialMode) { | |
| let jsonView; | |
| try { | |
| jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100); | |
| } catch(error) { | |
| jsonView = '(JSON view not available ' + error + ')'; | |
| } | |
| throw new Error('Data read, but end of buffer not reached ' + jsonView); | |
| } | |
| // else more to read, but we are reading sequentially, so don't clear source yet | |
| return result; | |
| } catch(error) { | |
| if (currentStructures && currentStructures.restoreStructures) | |
| restoreStructures(); | |
| clearSource(); | |
| if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) { | |
| error.incomplete = true; | |
| } | |
| throw error; | |
| } | |
| } | |
| function restoreStructures() { | |
| for (let id in currentStructures.restoreStructures) { | |
| currentStructures[id] = currentStructures.restoreStructures[id]; | |
| } | |
| currentStructures.restoreStructures = null; | |
| } | |
| function read() { | |
| let token = src[position$1++]; | |
| if (token < 0xa0) { | |
| if (token < 0x80) { | |
| if (token < 0x40) | |
| return token; | |
| else { | |
| let structure = currentStructures[token & 0x3f] || | |
| currentUnpackr.getStructures && loadStructures()[token & 0x3f]; | |
| if (structure) { | |
| if (!structure.read) { | |
| structure.read = createStructureReader(structure, token & 0x3f); | |
| } | |
| return structure.read(); | |
| } else | |
| return token; | |
| } | |
| } else if (token < 0x90) { | |
| // map | |
| token -= 0x80; | |
| if (currentUnpackr.mapsAsObjects) { | |
| let object = {}; | |
| for (let i = 0; i < token; i++) { | |
| let key = readKey(); | |
| if (key === '__proto__') | |
| key = '__proto_'; | |
| object[key] = read(); | |
| } | |
| return object; | |
| } else { | |
| let map = new Map(); | |
| for (let i = 0; i < token; i++) { | |
| map.set(read(), read()); | |
| } | |
| return map; | |
| } | |
| } else { | |
| token -= 0x90; | |
| let array = new Array(token); | |
| for (let i = 0; i < token; i++) { | |
| array[i] = read(); | |
| } | |
| if (currentUnpackr.freezeData) | |
| return Object.freeze(array); | |
| return array; | |
| } | |
| } else if (token < 0xc0) { | |
| // fixstr | |
| let length = token - 0xa0; | |
| if (srcStringEnd >= position$1) { | |
| return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart); | |
| } | |
| if (srcStringEnd == 0 && srcEnd < 140) { | |
| // for small blocks, avoiding the overhead of the extract call is helpful | |
| let string = length < 16 ? shortStringInJS(length) : longStringInJS(length); | |
| if (string != null) | |
| return string; | |
| } | |
| return readFixedString(length); | |
| } else { | |
| let value; | |
| switch (token) { | |
| case 0xc0: return null; | |
| case 0xc1: | |
| if (bundledStrings$1) { | |
| value = read(); // followed by the length of the string in characters (not bytes!) | |
| if (value > 0) | |
| return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value); | |
| else | |
| return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value); | |
| } | |
| return C1; // "never-used", return special object to denote that | |
| case 0xc2: return false; | |
| case 0xc3: return true; | |
| case 0xc4: | |
| // bin 8 | |
| value = src[position$1++]; | |
| if (value === undefined) | |
| throw new Error('Unexpected end of buffer'); | |
| return readBin(value); | |
| case 0xc5: | |
| // bin 16 | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| return readBin(value); | |
| case 0xc6: | |
| // bin 32 | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| return readBin(value); | |
| case 0xc7: | |
| // ext 8 | |
| return readExt(src[position$1++]); | |
| case 0xc8: | |
| // ext 16 | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| return readExt(value); | |
| case 0xc9: | |
| // ext 32 | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| return readExt(value); | |
| case 0xca: | |
| value = dataView.getFloat32(position$1); | |
| if (currentUnpackr.useFloat32 > 2) { | |
| // this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved | |
| let multiplier = mult10[((src[position$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)]; | |
| position$1 += 4; | |
| return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier; | |
| } | |
| position$1 += 4; | |
| return value; | |
| case 0xcb: | |
| value = dataView.getFloat64(position$1); | |
| position$1 += 8; | |
| return value; | |
| // uint handlers | |
| case 0xcc: | |
| return src[position$1++]; | |
| case 0xcd: | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| return value; | |
| case 0xce: | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| return value; | |
| case 0xcf: | |
| if (currentUnpackr.int64AsType === 'number') { | |
| value = dataView.getUint32(position$1) * 0x100000000; | |
| value += dataView.getUint32(position$1 + 4); | |
| } else if (currentUnpackr.int64AsType === 'string') { | |
| value = dataView.getBigUint64(position$1).toString(); | |
| } else if (currentUnpackr.int64AsType === 'auto') { | |
| value = dataView.getBigUint64(position$1); | |
| if (value<=BigInt(2)<<BigInt(52)) value=Number(value); | |
| } else | |
| value = dataView.getBigUint64(position$1); | |
| position$1 += 8; | |
| return value; | |
| // int handlers | |
| case 0xd0: | |
| return dataView.getInt8(position$1++); | |
| case 0xd1: | |
| value = dataView.getInt16(position$1); | |
| position$1 += 2; | |
| return value; | |
| case 0xd2: | |
| value = dataView.getInt32(position$1); | |
| position$1 += 4; | |
| return value; | |
| case 0xd3: | |
| if (currentUnpackr.int64AsType === 'number') { | |
| value = dataView.getInt32(position$1) * 0x100000000; | |
| value += dataView.getUint32(position$1 + 4); | |
| } else if (currentUnpackr.int64AsType === 'string') { | |
| value = dataView.getBigInt64(position$1).toString(); | |
| } else if (currentUnpackr.int64AsType === 'auto') { | |
| value = dataView.getBigInt64(position$1); | |
| if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value); | |
| } else | |
| value = dataView.getBigInt64(position$1); | |
| position$1 += 8; | |
| return value; | |
| case 0xd4: | |
| // fixext 1 | |
| value = src[position$1++]; | |
| if (value == 0x72) { | |
| return recordDefinition(src[position$1++] & 0x3f); | |
| } else { | |
| let extension = currentExtensions[value]; | |
| if (extension) { | |
| if (extension.read) { | |
| position$1++; // skip filler byte | |
| return extension.read(read()); | |
| } else if (extension.noBuffer) { | |
| position$1++; // skip filler byte | |
| return extension(); | |
| } else | |
| return extension(src.subarray(position$1, ++position$1)); | |
| } else | |
| throw new Error('Unknown extension ' + value); | |
| } | |
| case 0xd5: | |
| // fixext 2 | |
| value = src[position$1]; | |
| if (value == 0x72) { | |
| position$1++; | |
| return recordDefinition(src[position$1++] & 0x3f, src[position$1++]); | |
| } else | |
| return readExt(2); | |
| case 0xd6: | |
| // fixext 4 | |
| return readExt(4); | |
| case 0xd7: | |
| // fixext 8 | |
| return readExt(8); | |
| case 0xd8: | |
| // fixext 16 | |
| return readExt(16); | |
| case 0xd9: | |
| // str 8 | |
| value = src[position$1++]; | |
| if (srcStringEnd >= position$1) { | |
| return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart); | |
| } | |
| return readString8(value); | |
| case 0xda: | |
| // str 16 | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| if (srcStringEnd >= position$1) { | |
| return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart); | |
| } | |
| return readString16(value); | |
| case 0xdb: | |
| // str 32 | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| if (srcStringEnd >= position$1) { | |
| return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart); | |
| } | |
| return readString32(value); | |
| case 0xdc: | |
| // array 16 | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| return readArray(value); | |
| case 0xdd: | |
| // array 32 | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| return readArray(value); | |
| case 0xde: | |
| // map 16 | |
| value = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| return readMap(value); | |
| case 0xdf: | |
| // map 32 | |
| value = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| return readMap(value); | |
| default: // negative int | |
| if (token >= 0xe0) | |
| return token - 0x100; | |
| if (token === undefined) { | |
| let error = new Error('Unexpected end of MessagePack data'); | |
| error.incomplete = true; | |
| throw error; | |
| } | |
| throw new Error('Unknown MessagePack token ' + token); | |
| } | |
| } | |
| } | |
| const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; | |
| function createStructureReader(structure, firstId) { | |
| function readObject() { | |
| // This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function | |
| if (readObject.count++ > inlineObjectReadThreshold) { | |
| let optimizedReadObject; | |
| try { | |
| optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') + | |
| '({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read); | |
| } catch(error) { | |
| // in CF workers, the new Function call could begin to fail at any point in time | |
| inlineObjectReadThreshold = Infinity; // disable going forward | |
| return readObject(); // recursively try again | |
| } | |
| structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync | |
| if (structure.highByte === 0) | |
| structure.read = createSecondByteReader(firstId, structure.read); | |
| return optimizedReadObject(); // second byte is already read, if there is one so immediately read object | |
| } | |
| let object = {}; | |
| for (let i = 0, l = structure.length; i < l; i++) { | |
| let key = structure[i]; | |
| if (key === '__proto__') | |
| key = '__proto_'; | |
| object[key] = read(); | |
| } | |
| if (currentUnpackr.freezeData) | |
| return Object.freeze(object); | |
| return object; | |
| } | |
| readObject.count = 0; | |
| // read0 is the un-wrapped body reader: it reads the record's values directly without | |
| // consuming a leading high byte. recordDefinition uses it for the immediate read that follows | |
| // a record definition (the high byte, if present, was already consumed). For highByte === 0 | |
| // structures the public reader is a second-byte reader (used by later references), but the | |
| // definition read itself must not consume that byte. | |
| structure.read0 = readObject; | |
| if (structure.highByte === 0) { | |
| return createSecondByteReader(firstId, readObject); | |
| } | |
| return readObject; | |
| } | |
| const createSecondByteReader = (firstId, read0) => { | |
| return function() { | |
| let highByte = src[position$1++]; | |
| if (highByte === 0) | |
| return read0(); | |
| let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5); | |
| let structure = currentStructures[id] || loadStructures()[id]; | |
| if (!structure) { | |
| throw new Error('Record id is not defined for ' + id); | |
| } | |
| if (!structure.read) | |
| structure.read = createStructureReader(structure, firstId); | |
| return structure.read(); | |
| }; | |
| }; | |
| function loadStructures() { | |
| let loadedStructures = saveState(() => { | |
| // save the state in case getStructures modifies our buffer | |
| src = null; | |
| return currentUnpackr.getStructures(); | |
| }); | |
| return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures); | |
| } | |
| var readFixedString = readStringJS; | |
| var readString8 = readStringJS; | |
| var readString16 = readStringJS; | |
| var readString32 = readStringJS; | |
| function setExtractor(extractStrings) { | |
| readFixedString = readString(1); | |
| readString8 = readString(2); | |
| readString16 = readString(3); | |
| readString32 = readString(5); | |
| function readString(headerLength) { | |
| return function readString(length) { | |
| let string = strings[stringPosition++]; | |
| if (string == null) { | |
| if (bundledStrings$1) | |
| return readStringJS(length); | |
| let byteOffset = src.byteOffset; | |
| let extraction = extractStrings(position$1 - headerLength + byteOffset, srcEnd + byteOffset, src.buffer); | |
| if (typeof extraction == 'string') { | |
| string = extraction; | |
| strings = EMPTY_ARRAY; | |
| } else { | |
| strings = extraction; | |
| stringPosition = 1; | |
| srcStringEnd = 1; // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings | |
| string = strings[0]; | |
| if (string === undefined) | |
| throw new Error('Unexpected end of buffer'); | |
| } | |
| } | |
| let srcStringLength = string.length; | |
| if (srcStringLength <= length) { | |
| position$1 += length; | |
| return string; | |
| } | |
| srcString = string; | |
| srcStringStart = position$1; | |
| srcStringEnd = position$1 + srcStringLength; | |
| position$1 += length; | |
| return string.slice(0, length); // we know we just want the beginning | |
| }; | |
| } | |
| } | |
| function readStringJS(length) { | |
| let result; | |
| if (length < 16) { | |
| if (result = shortStringInJS(length)) | |
| return result; | |
| } | |
| if (length > 64 && decoder) | |
| return decoder.decode(src.subarray(position$1, position$1 += length)); | |
| const end = position$1 + length; | |
| const units = []; | |
| result = ''; | |
| while (position$1 < end) { | |
| const byte1 = src[position$1++]; | |
| if ((byte1 & 0x80) === 0) { | |
| // 1 byte | |
| units.push(byte1); | |
| } else if ((byte1 & 0xe0) === 0xc0) { | |
| // 2 bytes | |
| const byte2 = src[position$1++] & 0x3f; | |
| const codePoint = ((byte1 & 0x1f) << 6) | byte2; | |
| // Reject overlong encoding: 2-byte sequences must encode values >= 0x80 | |
| if (codePoint < 0x80) { | |
| units.push(0xFFFD); // replacement character | |
| } else { | |
| units.push(codePoint); | |
| } | |
| } else if ((byte1 & 0xf0) === 0xe0) { | |
| // 3 bytes | |
| const byte2 = src[position$1++] & 0x3f; | |
| const byte3 = src[position$1++] & 0x3f; | |
| const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3; | |
| // Reject overlong encoding: 3-byte sequences must encode values >= 0x800 | |
| // Also reject surrogates (0xD800-0xDFFF) | |
| if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { | |
| units.push(0xFFFD); // replacement character | |
| } else { | |
| units.push(codePoint); | |
| } | |
| } else if ((byte1 & 0xf8) === 0xf0) { | |
| // 4 bytes | |
| const byte2 = src[position$1++] & 0x3f; | |
| const byte3 = src[position$1++] & 0x3f; | |
| const byte4 = src[position$1++] & 0x3f; | |
| let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; | |
| // Reject overlong encoding: 4-byte sequences must encode values >= 0x10000 | |
| // Also reject values > 0x10FFFF (maximum valid Unicode) | |
| if (unit < 0x10000 || unit > 0x10FFFF) { | |
| units.push(0xFFFD); // replacement character | |
| } else if (unit > 0xffff) { | |
| unit -= 0x10000; | |
| units.push(((unit >>> 10) & 0x3ff) | 0xd800); | |
| unit = 0xdc00 | (unit & 0x3ff); | |
| units.push(unit); | |
| } else { | |
| units.push(unit); | |
| } | |
| } else { | |
| units.push(0xFFFD); // replacement character for invalid lead byte | |
| } | |
| if (units.length >= 0x1000) { | |
| result += fromCharCode.apply(String, units); | |
| units.length = 0; | |
| } | |
| } | |
| if (units.length > 0) { | |
| result += fromCharCode.apply(String, units); | |
| } | |
| return result; | |
| } | |
| function readArray(length) { | |
| let array = new Array(length); | |
| for (let i = 0; i < length; i++) { | |
| array[i] = read(); | |
| } | |
| if (currentUnpackr.freezeData) | |
| return Object.freeze(array); | |
| return array; | |
| } | |
| function readMap(length) { | |
| if (currentUnpackr.mapsAsObjects) { | |
| let object = {}; | |
| for (let i = 0; i < length; i++) { | |
| let key = readKey(); | |
| if (key === '__proto__') | |
| key = '__proto_'; | |
| object[key] = read(); | |
| } | |
| return object; | |
| } else { | |
| let map = new Map(); | |
| for (let i = 0; i < length; i++) { | |
| map.set(read(), read()); | |
| } | |
| return map; | |
| } | |
| } | |
| var fromCharCode = String.fromCharCode; | |
| function longStringInJS(length) { | |
| let start = position$1; | |
| let bytes = new Array(length); | |
| for (let i = 0; i < length; i++) { | |
| const byte = src[position$1++]; | |
| if ((byte & 0x80) > 0) { | |
| position$1 = start; | |
| return; | |
| } | |
| bytes[i] = byte; | |
| } | |
| return fromCharCode.apply(String, bytes); | |
| } | |
| function shortStringInJS(length) { | |
| if (length < 4) { | |
| if (length < 2) { | |
| if (length === 0) | |
| return ''; | |
| else { | |
| let a = src[position$1++]; | |
| if ((a & 0x80) > 1) { | |
| position$1 -= 1; | |
| return; | |
| } | |
| return fromCharCode(a); | |
| } | |
| } else { | |
| let a = src[position$1++]; | |
| let b = src[position$1++]; | |
| if ((a & 0x80) > 0 || (b & 0x80) > 0) { | |
| position$1 -= 2; | |
| return; | |
| } | |
| if (length < 3) | |
| return fromCharCode(a, b); | |
| let c = src[position$1++]; | |
| if ((c & 0x80) > 0) { | |
| position$1 -= 3; | |
| return; | |
| } | |
| return fromCharCode(a, b, c); | |
| } | |
| } else { | |
| let a = src[position$1++]; | |
| let b = src[position$1++]; | |
| let c = src[position$1++]; | |
| let d = src[position$1++]; | |
| if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) { | |
| position$1 -= 4; | |
| return; | |
| } | |
| if (length < 6) { | |
| if (length === 4) | |
| return fromCharCode(a, b, c, d); | |
| else { | |
| let e = src[position$1++]; | |
| if ((e & 0x80) > 0) { | |
| position$1 -= 5; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e); | |
| } | |
| } else if (length < 8) { | |
| let e = src[position$1++]; | |
| let f = src[position$1++]; | |
| if ((e & 0x80) > 0 || (f & 0x80) > 0) { | |
| position$1 -= 6; | |
| return; | |
| } | |
| if (length < 7) | |
| return fromCharCode(a, b, c, d, e, f); | |
| let g = src[position$1++]; | |
| if ((g & 0x80) > 0) { | |
| position$1 -= 7; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e, f, g); | |
| } else { | |
| let e = src[position$1++]; | |
| let f = src[position$1++]; | |
| let g = src[position$1++]; | |
| let h = src[position$1++]; | |
| if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) { | |
| position$1 -= 8; | |
| return; | |
| } | |
| if (length < 10) { | |
| if (length === 8) | |
| return fromCharCode(a, b, c, d, e, f, g, h); | |
| else { | |
| let i = src[position$1++]; | |
| if ((i & 0x80) > 0) { | |
| position$1 -= 9; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e, f, g, h, i); | |
| } | |
| } else if (length < 12) { | |
| let i = src[position$1++]; | |
| let j = src[position$1++]; | |
| if ((i & 0x80) > 0 || (j & 0x80) > 0) { | |
| position$1 -= 10; | |
| return; | |
| } | |
| if (length < 11) | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j); | |
| let k = src[position$1++]; | |
| if ((k & 0x80) > 0) { | |
| position$1 -= 11; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j, k); | |
| } else { | |
| let i = src[position$1++]; | |
| let j = src[position$1++]; | |
| let k = src[position$1++]; | |
| let l = src[position$1++]; | |
| if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) { | |
| position$1 -= 12; | |
| return; | |
| } | |
| if (length < 14) { | |
| if (length === 12) | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l); | |
| else { | |
| let m = src[position$1++]; | |
| if ((m & 0x80) > 0) { | |
| position$1 -= 13; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m); | |
| } | |
| } else { | |
| let m = src[position$1++]; | |
| let n = src[position$1++]; | |
| if ((m & 0x80) > 0 || (n & 0x80) > 0) { | |
| position$1 -= 14; | |
| return; | |
| } | |
| if (length < 15) | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n); | |
| let o = src[position$1++]; | |
| if ((o & 0x80) > 0) { | |
| position$1 -= 15; | |
| return; | |
| } | |
| return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function readOnlyJSString() { | |
| let token = src[position$1++]; | |
| let length; | |
| if (token < 0xc0) { | |
| // fixstr | |
| length = token - 0xa0; | |
| } else { | |
| switch(token) { | |
| case 0xd9: | |
| // str 8 | |
| length = src[position$1++]; | |
| break; | |
| case 0xda: | |
| // str 16 | |
| length = dataView.getUint16(position$1); | |
| position$1 += 2; | |
| break; | |
| case 0xdb: | |
| // str 32 | |
| length = dataView.getUint32(position$1); | |
| position$1 += 4; | |
| break; | |
| default: | |
| throw new Error('Expected string'); | |
| } | |
| } | |
| return readStringJS(length); | |
| } | |
| function readBin(length) { | |
| return currentUnpackr.copyBuffers ? | |
| // specifically use the copying slice (not the node one) | |
| Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : | |
| src.subarray(position$1, position$1 += length); | |
| } | |
| function readExt(length) { | |
| let type = src[position$1++]; | |
| if (currentExtensions[type]) { | |
| let end; | |
| return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => { | |
| position$1 = readPosition; | |
| try { | |
| return read(); | |
| } finally { | |
| position$1 = end; | |
| } | |
| }); | |
| } | |
| else | |
| throw new Error('Unknown extension type ' + type); | |
| } | |
| var keyCache = new Array(4096); | |
| function readKey() { | |
| let length = src[position$1++]; | |
| if (length >= 0xa0 && length < 0xc0) { | |
| // fixstr, potentially use key cache | |
| length = length - 0xa0; | |
| if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway) | |
| return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart); | |
| else if (!(srcStringEnd == 0 && srcEnd < 180)) | |
| return readFixedString(length); | |
| } else { // not cacheable, go back and do a standard read | |
| position$1--; | |
| return asSafeString(read()); | |
| } | |
| let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff; | |
| let entry = keyCache[key]; | |
| let checkPosition = position$1; | |
| let end = position$1 + length - 3; | |
| let chunk; | |
| let i = 0; | |
| if (entry && entry.bytes == length) { | |
| while (checkPosition < end) { | |
| chunk = dataView.getUint32(checkPosition); | |
| if (chunk != entry[i++]) { | |
| checkPosition = 0x70000000; | |
| break; | |
| } | |
| checkPosition += 4; | |
| } | |
| end += 3; | |
| while (checkPosition < end) { | |
| chunk = src[checkPosition++]; | |
| if (chunk != entry[i++]) { | |
| checkPosition = 0x70000000; | |
| break; | |
| } | |
| } | |
| if (checkPosition === end) { | |
| position$1 = checkPosition; | |
| return entry.string; | |
| } | |
| end -= 3; | |
| checkPosition = position$1; | |
| } | |
| entry = []; | |
| keyCache[key] = entry; | |
| entry.bytes = length; | |
| while (checkPosition < end) { | |
| chunk = dataView.getUint32(checkPosition); | |
| entry.push(chunk); | |
| checkPosition += 4; | |
| } | |
| end += 3; | |
| while (checkPosition < end) { | |
| chunk = src[checkPosition++]; | |
| entry.push(chunk); | |
| } | |
| // for small blocks, avoiding the overhead of the extract call is helpful | |
| let string = length < 16 ? shortStringInJS(length) : longStringInJS(length); | |
| if (string != null) | |
| return entry.string = string; | |
| return entry.string = readFixedString(length); | |
| } | |
| function asSafeString(property) { | |
| // protect against expensive (DoS) string conversions | |
| if (typeof property === 'string') return property; | |
| if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString(); | |
| if (property == null) return property + ''; | |
| if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) { | |
| return property.flat().toString(); | |
| } | |
| throw new Error(`Invalid property type for record: ${typeof property}`); | |
| } | |
| // the registration of the record definition extension (as "r") | |
| const recordDefinition = (id, highByte) => { | |
| let structure = read().map(asSafeString); // ensure that all keys are strings and | |
| // that the array is mutable | |
| let firstByte = id; | |
| if (highByte !== undefined) { | |
| id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id); | |
| structure.highByte = highByte; | |
| } | |
| let existingStructure = currentStructures[id]; | |
| // If it is a shared structure, we need to restore any changes after reading. | |
| // Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore | |
| // to the state prior to an incomplete read in order to properly resume. | |
| if (existingStructure && (existingStructure.isShared || sequentialMode)) { | |
| (currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure; | |
| } | |
| currentStructures[id] = structure; | |
| structure.read = createStructureReader(structure, firstByte); | |
| // The high byte (if any) was already consumed as the `highByte` argument above, so read the | |
| // record body directly. Going through structure.read (a second-byte reader when highByte === 0) | |
| // would misinterpret the first value byte as a high byte — corrupting two-byte own-record | |
| // definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on | |
| // structure.read0 precisely for this immediate post-definition read. | |
| return (structure.read0 || structure.read)(); | |
| }; | |
| currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here | |
| currentExtensions[0].noBuffer = true; | |
| currentExtensions[0x42] = data => { | |
| let headLength = (data.byteLength % 8) || 8; | |
| let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]); | |
| for (let i = 1; i < headLength; i++) { | |
| head <<= BigInt(8); | |
| head += BigInt(data[i]); | |
| } | |
| if (data.byteLength !== headLength) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let decode = (start, end) => { | |
| let length = end - start; | |
| if (length <= 40) { | |
| let out = view.getBigUint64(start); | |
| for (let i = start + 8; i < end; i += 8) { | |
| out <<= BigInt(64); | |
| out |= view.getBigUint64(i); | |
| } | |
| return out; | |
| } | |
| // if (length === 8) return view.getBigUint64(start) | |
| let middle = start + (length >> 4 << 3); | |
| let left = decode(start, middle); | |
| let right = decode(middle, end); | |
| return (left << BigInt((end - middle) * 8)) | right; | |
| }; | |
| head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength); | |
| } | |
| return head; | |
| }; | |
| let errors = { | |
| Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null, | |
| }; | |
| currentExtensions[0x65] = () => { | |
| let data = read(); | |
| if (!errors[data[0]]) { | |
| let error = Error(data[1], { cause: data[2] }); | |
| error.name = data[0]; | |
| return error; | |
| } | |
| return errors[data[0]](data[1], { cause: data[2] }); | |
| }; | |
| currentExtensions[0x69] = (data) => { | |
| // id extension (for structured clones) | |
| if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled'); | |
| let id = dataView.getUint32(position$1 - 4); | |
| if (!referenceMap) | |
| referenceMap = new Map(); | |
| let token = src[position$1]; | |
| let target; | |
| // TODO: handle any other types that can cycle and make the code more robust if there are other extensions | |
| if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd) | |
| target = []; | |
| else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf) | |
| target = new Map(); | |
| else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73) | |
| target = new Set(); | |
| else | |
| target = {}; | |
| let refEntry = { target }; // a placeholder object | |
| referenceMap.set(id, refEntry); | |
| let targetProperties = read(); // read the next value as the target object to id | |
| if (!refEntry.used) { | |
| // no cycle, can just use the returned read object | |
| return refEntry.target = targetProperties; // replace the placeholder with the real one | |
| } else { | |
| // there is a cycle, so we have to assign properties to original target | |
| Object.assign(target, targetProperties); | |
| } | |
| // copy over map/set entries if we're able to | |
| if (target instanceof Map) | |
| for (let [k, v] of targetProperties.entries()) target.set(k, v); | |
| if (target instanceof Set) | |
| for (let i of Array.from(targetProperties)) target.add(i); | |
| return target; | |
| }; | |
| currentExtensions[0x70] = (data) => { | |
| // pointer extension (for structured clones) | |
| if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled'); | |
| let id = dataView.getUint32(position$1 - 4); | |
| let refEntry = referenceMap.get(id); | |
| refEntry.used = true; | |
| return refEntry.target; | |
| }; | |
| currentExtensions[0x73] = () => new Set(read()); | |
| const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array'); | |
| let glbl = typeof globalThis === 'object' ? globalThis : window; | |
| currentExtensions[0x74] = (data) => { | |
| let typeCode = data[0]; | |
| // we always have to slice to get a new ArrayBuffer that is aligned | |
| let buffer = Uint8Array.prototype.slice.call(data, 1).buffer; | |
| let typedArrayName = typedArrays[typeCode]; | |
| if (!typedArrayName) { | |
| if (typeCode === 16) return buffer; | |
| if (typeCode === 17) return new DataView(buffer); | |
| throw new Error('Could not find typed array for code ' + typeCode); | |
| } | |
| return new glbl[typedArrayName](buffer); | |
| }; | |
| currentExtensions[0x78] = () => { | |
| let data = read(); | |
| return new RegExp(data[0], data[1]); | |
| }; | |
| const TEMP_BUNDLE = []; | |
| currentExtensions[0x62] = (data) => { | |
| let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]; | |
| let dataPosition = position$1; | |
| position$1 += dataSize - data.length; | |
| bundledStrings$1 = TEMP_BUNDLE; | |
| bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()]; | |
| bundledStrings$1.position0 = 0; | |
| bundledStrings$1.position1 = 0; | |
| bundledStrings$1.postBundlePosition = position$1; | |
| position$1 = dataPosition; | |
| return read(); | |
| }; | |
| currentExtensions[0xff] = (data) => { | |
| // 32-bit date extension | |
| if (data.length == 4) | |
| return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000); | |
| else if (data.length == 8) | |
| return new Date( | |
| ((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 + | |
| ((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000); | |
| else if (data.length == 12) | |
| return new Date( | |
| ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 + | |
| (((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000); | |
| else | |
| return new Date('invalid'); | |
| }; | |
| // registration of bulk record definition? | |
| // currentExtensions[0x52] = () => | |
| function saveState(callback) { | |
| if (currentUnpackr && currentUnpackr._onSaveState) | |
| currentUnpackr._onSaveState(); | |
| let savedSrcEnd = srcEnd; | |
| let savedPosition = position$1; | |
| let savedStringPosition = stringPosition; | |
| let savedSrcStringStart = srcStringStart; | |
| let savedSrcStringEnd = srcStringEnd; | |
| let savedSrcString = srcString; | |
| let savedStrings = strings; | |
| let savedReferenceMap = referenceMap; | |
| let savedBundledStrings = bundledStrings$1; | |
| // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow) | |
| let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed | |
| let savedStructures = currentStructures; | |
| let savedStructuresContents = currentStructures.slice(0, currentStructures.length); | |
| let savedPackr = currentUnpackr; | |
| let savedSequentialMode = sequentialMode; | |
| let value = callback(); | |
| srcEnd = savedSrcEnd; | |
| position$1 = savedPosition; | |
| stringPosition = savedStringPosition; | |
| srcStringStart = savedSrcStringStart; | |
| srcStringEnd = savedSrcStringEnd; | |
| srcString = savedSrcString; | |
| strings = savedStrings; | |
| referenceMap = savedReferenceMap; | |
| bundledStrings$1 = savedBundledStrings; | |
| src = savedSrc; | |
| sequentialMode = savedSequentialMode; | |
| currentStructures = savedStructures; | |
| currentStructures.splice(0, currentStructures.length, ...savedStructuresContents); | |
| currentUnpackr = savedPackr; | |
| dataView = new DataView(src.buffer, src.byteOffset, src.byteLength); | |
| return value; | |
| } | |
| function clearSource() { | |
| src = null; | |
| referenceMap = null; | |
| currentStructures = null; | |
| } | |
| function addExtension$2(extension) { | |
| if (extension.unpack) | |
| currentExtensions[extension.type] = extension.unpack; | |
| else | |
| currentExtensions[extension.type] = extension; | |
| } | |
| const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding | |
| for (let i = 0; i < 256; i++) { | |
| mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103)); | |
| } | |
| var defaultUnpackr = new Unpackr$1({ useRecords: false }); | |
| const unpack$1 = defaultUnpackr.unpack; | |
| const unpackMultiple$1 = defaultUnpackr.unpackMultiple; | |
| defaultUnpackr.unpack; | |
| const FLOAT32_OPTIONS = { | |
| NEVER: 0, | |
| ALWAYS: 1, | |
| DECIMAL_ROUND: 3, | |
| DECIMAL_FIT: 4 | |
| }; | |
| let f32Array = new Float32Array(1); | |
| let u8Array = new Uint8Array(f32Array.buffer, 0, 4); | |
| function roundFloat32$1(float32Number) { | |
| f32Array[0] = float32Number; | |
| let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]; | |
| return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier; | |
| } | |
| // Marker for downstream libraries (e.g. structon) to detect per-instance | |
| // struct-decoding hooks (this._readStruct, this._onLoadedStructures, | |
| // this._onSaveState). See `checkedRead` for the dispatch. | |
| Unpackr$1.SUPPORTS_STRUCT_HOOKS = true; | |
| let textEncoder; | |
| try { | |
| textEncoder = new TextEncoder(); | |
| } catch (error) {} | |
| let extensions, extensionClasses; | |
| const hasNodeBuffer = typeof Buffer !== 'undefined'; | |
| const ByteArrayAllocate = hasNodeBuffer ? | |
| function(length) { return Buffer.allocUnsafeSlow(length); } : Uint8Array; | |
| const ByteArray = hasNodeBuffer ? Buffer : Uint8Array; | |
| const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000; | |
| let target, keysTarget; | |
| let targetView; | |
| let position = 0; | |
| let safeEnd; | |
| let bundledStrings = null; | |
| const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits. | |
| const hasNonLatin = /[\u0080-\uFFFF]/; | |
| const RECORD_SYMBOL = Symbol('record-id'); | |
| let Packr$1 = class Packr extends Unpackr$1 { | |
| constructor(options) { | |
| super(options); | |
| this.offset = 0; | |
| let start; | |
| let hasSharedUpdate; | |
| let structures; | |
| let referenceMap; | |
| let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) { | |
| return target.utf8Write(string, position, target.byteLength - position); | |
| } : (textEncoder && textEncoder.encodeInto) ? | |
| function(string, position) { | |
| return textEncoder.encodeInto(string, target.subarray(position)).written; | |
| } : false; | |
| let packr = this; | |
| if (!options) | |
| options = {}; | |
| let isSequential = options && options.sequential; | |
| let hasSharedStructures = options.structures || options.saveStructures; | |
| let maxSharedStructures = options.maxSharedStructures; | |
| if (maxSharedStructures == null) | |
| maxSharedStructures = hasSharedStructures ? 32 : 0; | |
| if (maxSharedStructures > 8160) | |
| throw new Error('Maximum maxSharedStructure is 8160'); | |
| if (options.structuredClone && options.moreTypes == undefined) { | |
| this.moreTypes = true; | |
| } | |
| let maxOwnStructures = options.maxOwnStructures; | |
| if (maxOwnStructures == null) | |
| maxOwnStructures = hasSharedStructures ? 32 : 64; | |
| if (!this.structures && options.useRecords != false) | |
| this.structures = []; | |
| // two byte record ids for shared structures | |
| let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64); | |
| let sharedLimitId = maxSharedStructures + 0x40; | |
| let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40; | |
| if (maxStructureId > 8256) { | |
| throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192'); | |
| } | |
| let recordIdsToRemove = []; | |
| let transitionsCount = 0; | |
| let serializationsSinceTransitionRebuild = 0; | |
| this.pack = this.encode = function(value, encodeOptions) { | |
| if (!target) { | |
| target = new ByteArrayAllocate(8192); | |
| targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192)); | |
| position = 0; | |
| } | |
| safeEnd = target.length - 10; | |
| if (safeEnd - position < 0x800) { | |
| // don't start too close to the end, | |
| target = new ByteArrayAllocate(target.length); | |
| targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length)); | |
| safeEnd = target.length - 10; | |
| position = 0; | |
| } else | |
| position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster | |
| start = position; | |
| if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff); | |
| referenceMap = packr.structuredClone ? new Map() : null; | |
| if (packr.bundleStrings && typeof value !== 'string') { | |
| bundledStrings = []; | |
| bundledStrings.size = Infinity; // force a new bundle start on first string | |
| } else | |
| bundledStrings = null; | |
| structures = packr.structures; | |
| if (structures) { | |
| if (structures.uninitialized) | |
| structures = packr._mergeStructures(packr.getStructures()); | |
| let sharedLength = structures.sharedLength || 0; | |
| if (sharedLength > maxSharedStructures) { | |
| //if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids | |
| throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength); | |
| } | |
| if (!structures.transitions) { | |
| // rebuild our structure transitions | |
| structures.transitions = Object.create(null); | |
| for (let i = 0; i < sharedLength; i++) { | |
| let keys = structures[i]; | |
| if (!keys) | |
| continue; | |
| let nextTransition, transition = structures.transitions; | |
| for (let j = 0, l = keys.length; j < l; j++) { | |
| let key = keys[j]; | |
| nextTransition = transition[key]; | |
| if (!nextTransition) { | |
| nextTransition = transition[key] = Object.create(null); | |
| } | |
| transition = nextTransition; | |
| } | |
| transition[RECORD_SYMBOL] = i + 0x40; | |
| } | |
| this.lastNamedStructuresLength = sharedLength; | |
| } | |
| if (!isSequential) { | |
| structures.nextId = sharedLength + 0x40; | |
| } | |
| } | |
| if (hasSharedUpdate) | |
| hasSharedUpdate = false; | |
| let encodingError; | |
| try { | |
| if (packr._writeStruct && value && typeof value === 'object') { | |
| if (value.constructor === Object) writeStruct(value); // simple object | |
| else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) { | |
| // allow user classes, if they don't need special handling (but do use toJSON if available) | |
| writeStruct(value.toJSON ? value.toJSON() : value); | |
| } else pack(value); | |
| } else | |
| pack(value); | |
| let lastBundle = bundledStrings; | |
| if (bundledStrings) | |
| writeBundles(start, pack, 0); | |
| if (referenceMap && referenceMap.idsToInsert) { | |
| let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1); | |
| let i = idsToInsert.length; | |
| let incrementPosition = -1; | |
| while (lastBundle && i > 0) { | |
| let insertionPoint = idsToInsert[--i].offset + start; | |
| if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1) | |
| incrementPosition = 0; | |
| if (insertionPoint > (lastBundle.position + start)) { | |
| if (incrementPosition >= 0) | |
| incrementPosition += 6; | |
| } else { | |
| if (incrementPosition >= 0) { | |
| // update the bundle reference now | |
| targetView.setUint32(lastBundle.position + start, | |
| targetView.getUint32(lastBundle.position + start) + incrementPosition); | |
| incrementPosition = -1; // reset | |
| } | |
| lastBundle = lastBundle.previous; | |
| i++; | |
| } | |
| } | |
| if (incrementPosition >= 0 && lastBundle) { | |
| // update the bundle reference now | |
| targetView.setUint32(lastBundle.position + start, | |
| targetView.getUint32(lastBundle.position + start) + incrementPosition); | |
| } | |
| position += idsToInsert.length * 6; | |
| if (position > safeEnd) | |
| makeRoom(position); | |
| packr.offset = position; | |
| let serialized = insertIds(target.subarray(start, position), idsToInsert); | |
| referenceMap = null; | |
| return serialized; | |
| } | |
| packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially | |
| if (encodeOptions & REUSE_BUFFER_MODE) { | |
| target.start = start; | |
| target.end = position; | |
| return target; | |
| } | |
| return target.subarray(start, position); // position can change if we call pack again in saveStructures, so we get the buffer now | |
| } catch(error) { | |
| encodingError = error; | |
| throw error; | |
| } finally { | |
| if (structures) { | |
| resetStructures(); | |
| if (hasSharedUpdate && packr.saveStructures) { | |
| let sharedLength = structures.sharedLength || 0; | |
| // we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save | |
| let returnBuffer = target.subarray(start, position); | |
| let newSharedData = (packr._prepareStructures || prepareStructures)(structures, packr); | |
| if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time | |
| if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) { | |
| // The save was declined (a concurrent writer updated the shared structures, | |
| // or the store transaction did not durably commit). Our in-memory | |
| // structures + transition trie may now reference record ids that were | |
| // never persisted; re-packing as-is would re-emit the same record pointing | |
| // at an unpersisted structure (-> "Record id is not defined" on decode). | |
| // Mark structures uninitialized so the re-pack reloads durable structures | |
| // via getStructures, rebuilds the transition trie, and re-mints + re-saves. | |
| structures.uninitialized = true; | |
| return packr.pack(value, encodeOptions); | |
| } | |
| packr.lastNamedStructuresLength = sharedLength; | |
| // don't keep large buffers around | |
| if (target.length > 0x40000000) target = null; | |
| return returnBuffer; | |
| } | |
| } | |
| } | |
| // don't keep large buffers around, they take too much memory and cause problems (limit at 1GB) | |
| if (target.length > 0x40000000) target = null; | |
| if (encodeOptions & RESET_BUFFER_MODE) | |
| position = start; | |
| } | |
| }; | |
| const resetStructures = () => { | |
| if (serializationsSinceTransitionRebuild < 10) | |
| serializationsSinceTransitionRebuild++; | |
| let sharedLength = structures.sharedLength || 0; | |
| if (structures.length > sharedLength && !isSequential) | |
| structures.length = sharedLength; | |
| if (transitionsCount > 10000) { | |
| // force a rebuild occasionally after a lot of transitions so it can get cleaned up | |
| structures.transitions = null; | |
| serializationsSinceTransitionRebuild = 0; | |
| transitionsCount = 0; | |
| if (recordIdsToRemove.length > 0) | |
| recordIdsToRemove = []; | |
| } else if (recordIdsToRemove.length > 0 && !isSequential) { | |
| for (let i = 0, l = recordIdsToRemove.length; i < l; i++) { | |
| recordIdsToRemove[i][RECORD_SYMBOL] = 0; | |
| } | |
| recordIdsToRemove = []; | |
| } | |
| }; | |
| const packArray = (value) => { | |
| var length = value.length; | |
| if (length < 0x10) { | |
| target[position++] = 0x90 | length; | |
| } else if (length < 0x10000) { | |
| target[position++] = 0xdc; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| target[position++] = 0xdd; | |
| targetView.setUint32(position, length); | |
| position += 4; | |
| } | |
| for (let i = 0; i < length; i++) { | |
| pack(value[i]); | |
| } | |
| }; | |
| const pack = (value) => { | |
| if (position > safeEnd) | |
| target = makeRoom(position); | |
| var type = typeof value; | |
| var length; | |
| if (type === 'string') { | |
| let strLength = value.length; | |
| if (bundledStrings && strLength >= 4 && strLength < 0x1000) { | |
| if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) { | |
| let extStart; | |
| let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10; | |
| if (position + maxBytes > safeEnd) | |
| target = makeRoom(position + maxBytes); | |
| let lastBundle; | |
| if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle | |
| lastBundle = bundledStrings; | |
| target[position] = 0xc8; // ext 16 | |
| position += 3; // reserve for the writing bundle size | |
| target[position++] = 0x62; // 'b' | |
| extStart = position - start; | |
| position += 4; // reserve for writing bundle reference | |
| writeBundles(start, pack, 0); // write the last bundles | |
| targetView.setUint16(extStart + start - 3, position - start - extStart); | |
| } else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written) | |
| target[position++] = 0xd6; // fixext 4 | |
| target[position++] = 0x62; // 'b' | |
| extStart = position - start; | |
| position += 4; // reserve for writing bundle reference | |
| } | |
| bundledStrings = ['', '']; // create new ones | |
| bundledStrings.previous = lastBundle; | |
| bundledStrings.size = 0; | |
| bundledStrings.position = extStart; | |
| } | |
| let twoByte = hasNonLatin.test(value); | |
| bundledStrings[twoByte ? 0 : 1] += value; | |
| target[position++] = 0xc1; | |
| pack(twoByte ? -strLength : strLength); | |
| return; | |
| } | |
| let headerSize; | |
| // first we estimate the header size, so we can write to the correct location | |
| if (strLength < 0x20) { | |
| headerSize = 1; | |
| } else if (strLength < 0x100) { | |
| headerSize = 2; | |
| } else if (strLength < 0x10000) { | |
| headerSize = 3; | |
| } else { | |
| headerSize = 5; | |
| } | |
| let maxBytes = strLength * 3; | |
| if (position + maxBytes > safeEnd) | |
| target = makeRoom(position + maxBytes); | |
| if (strLength < 0x40 || !encodeUtf8) { | |
| let i, c1, c2, strPosition = position + headerSize; | |
| for (i = 0; i < strLength; i++) { | |
| c1 = value.charCodeAt(i); | |
| if (c1 < 0x80) { | |
| target[strPosition++] = c1; | |
| } else if (c1 < 0x800) { | |
| target[strPosition++] = c1 >> 6 | 0xc0; | |
| target[strPosition++] = c1 & 0x3f | 0x80; | |
| } else if ( | |
| (c1 & 0xfc00) === 0xd800 && | |
| ((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00 | |
| ) { | |
| c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff); | |
| i++; | |
| target[strPosition++] = c1 >> 18 | 0xf0; | |
| target[strPosition++] = c1 >> 12 & 0x3f | 0x80; | |
| target[strPosition++] = c1 >> 6 & 0x3f | 0x80; | |
| target[strPosition++] = c1 & 0x3f | 0x80; | |
| } else { | |
| target[strPosition++] = c1 >> 12 | 0xe0; | |
| target[strPosition++] = c1 >> 6 & 0x3f | 0x80; | |
| target[strPosition++] = c1 & 0x3f | 0x80; | |
| } | |
| } | |
| length = strPosition - position - headerSize; | |
| } else { | |
| length = encodeUtf8(value, position + headerSize); | |
| } | |
| if (length < 0x20) { | |
| target[position++] = 0xa0 | length; | |
| } else if (length < 0x100) { | |
| if (headerSize < 2) { | |
| target.copyWithin(position + 2, position + 1, position + 1 + length); | |
| } | |
| target[position++] = 0xd9; | |
| target[position++] = length; | |
| } else if (length < 0x10000) { | |
| if (headerSize < 3) { | |
| target.copyWithin(position + 3, position + 2, position + 2 + length); | |
| } | |
| target[position++] = 0xda; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| if (headerSize < 5) { | |
| target.copyWithin(position + 5, position + 3, position + 3 + length); | |
| } | |
| target[position++] = 0xdb; | |
| targetView.setUint32(position, length); | |
| position += 4; | |
| } | |
| position += length; | |
| } else if (type === 'number') { | |
| if (value >>> 0 === value) {// positive integer, 32-bit or less | |
| // positive uint | |
| if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this._writeStruct)) { | |
| target[position++] = value; | |
| } else if (value < 0x100) { | |
| target[position++] = 0xcc; | |
| target[position++] = value; | |
| } else if (value < 0x10000) { | |
| target[position++] = 0xcd; | |
| target[position++] = value >> 8; | |
| target[position++] = value & 0xff; | |
| } else { | |
| target[position++] = 0xce; | |
| targetView.setUint32(position, value); | |
| position += 4; | |
| } | |
| } else if (value >> 0 === value) { // negative integer | |
| if (value >= -0x20) { | |
| target[position++] = 0x100 + value; | |
| } else if (value >= -0x80) { | |
| target[position++] = 0xd0; | |
| target[position++] = value + 0x100; | |
| } else if (value >= -0x8000) { | |
| target[position++] = 0xd1; | |
| targetView.setInt16(position, value); | |
| position += 2; | |
| } else { | |
| target[position++] = 0xd2; | |
| targetView.setInt32(position, value); | |
| position += 4; | |
| } | |
| } else { | |
| let useFloat32; | |
| if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) { | |
| target[position++] = 0xca; | |
| targetView.setFloat32(position, value); | |
| let xShifted; | |
| if (useFloat32 < 4 || | |
| // this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved | |
| ((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) { | |
| position += 4; | |
| return; | |
| } else | |
| position--; // move back into position for writing a double | |
| } | |
| target[position++] = 0xcb; | |
| targetView.setFloat64(position, value); | |
| position += 8; | |
| } | |
| } else if (type === 'object' || type === 'function') { | |
| if (!value) | |
| target[position++] = 0xc0; | |
| else { | |
| if (referenceMap) { | |
| let referee = referenceMap.get(value); | |
| if (referee) { | |
| if (!referee.id) { | |
| let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []); | |
| referee.id = idsToInsert.push(referee); | |
| } | |
| target[position++] = 0xd6; // fixext 4 | |
| target[position++] = 0x70; // "p" for pointer | |
| targetView.setUint32(position, referee.id); | |
| position += 4; | |
| return; | |
| } else | |
| referenceMap.set(value, { offset: position - start }); | |
| } | |
| let constructor = value.constructor; | |
| if (constructor === Object) { | |
| writeObject(value); | |
| } else if (constructor === Array) { | |
| packArray(value); | |
| } else if (constructor === Map) { | |
| if (this.mapAsEmptyObject) target[position++] = 0x80; | |
| else { | |
| length = value.size; | |
| if (length < 0x10) { | |
| target[position++] = 0x80 | length; | |
| } else if (length < 0x10000) { | |
| target[position++] = 0xde; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| target[position++] = 0xdf; | |
| targetView.setUint32(position, length); | |
| position += 4; | |
| } | |
| for (let [key, entryValue] of value) { | |
| pack(key); | |
| pack(entryValue); | |
| } | |
| } | |
| } else { | |
| for (let i = 0, l = extensions.length; i < l; i++) { | |
| let extensionClass = extensionClasses[i]; | |
| if (value instanceof extensionClass) { | |
| let extension = extensions[i]; | |
| if (extension.write) { | |
| if (extension.type) { | |
| target[position++] = 0xd4; // one byte "tag" extension | |
| target[position++] = extension.type; | |
| target[position++] = 0; | |
| } | |
| let writeResult = extension.write.call(this, value); | |
| if (writeResult === value) { // avoid infinite recursion | |
| if (Array.isArray(value)) { | |
| packArray(value); | |
| } else { | |
| writeObject(value); | |
| } | |
| } else { | |
| pack(writeResult); | |
| } | |
| return; | |
| } | |
| let currentTarget = target; | |
| let currentTargetView = targetView; | |
| let currentPosition = position; | |
| target = null; | |
| let result; | |
| try { | |
| result = extension.pack.call(this, value, (size) => { | |
| // restore target and use it | |
| target = currentTarget; | |
| currentTarget = null; | |
| position += size; | |
| if (position > safeEnd) | |
| makeRoom(position); | |
| return { | |
| target, targetView, position: position - size | |
| }; | |
| }, pack); | |
| } finally { | |
| // restore current target information (unless already restored) | |
| if (currentTarget) { | |
| target = currentTarget; | |
| targetView = currentTargetView; | |
| position = currentPosition; | |
| safeEnd = target.length - 10; | |
| } | |
| } | |
| if (result) { | |
| if (result.length + position > safeEnd) | |
| makeRoom(result.length + position); | |
| position = writeExtensionData(result, target, position, extension.type); | |
| } | |
| return; | |
| } | |
| } | |
| // check isArray after extensions, because extensions can extend Array | |
| if (Array.isArray(value)) { | |
| packArray(value); | |
| } else { | |
| // use this as an alternate mechanism for expressing how to serialize | |
| if (value.toJSON) { | |
| const json = value.toJSON(); | |
| // if for some reason value.toJSON returns itself it'll loop forever | |
| if (json !== value) | |
| return pack(json); | |
| } | |
| // if there is a writeFunction, use it, otherwise just encode as undefined | |
| if (type === 'function') | |
| return pack(this.writeFunction && this.writeFunction(value)); | |
| // no extension found, write as plain object | |
| writeObject(value); | |
| } | |
| } | |
| } | |
| } else if (type === 'boolean') { | |
| target[position++] = value ? 0xc3 : 0xc2; | |
| } else if (type === 'bigint') { | |
| if (value < 0x8000000000000000 && value >= -0x8000000000000000) { | |
| // use a signed int as long as it fits | |
| target[position++] = 0xd3; | |
| targetView.setBigInt64(position, value); | |
| } else if (value < 0x10000000000000000 && value > 0) { | |
| // if we can fit an unsigned int, use that | |
| target[position++] = 0xcf; | |
| targetView.setBigUint64(position, value); | |
| } else { | |
| // overflow | |
| if (this.largeBigIntToFloat) { | |
| target[position++] = 0xcb; | |
| targetView.setFloat64(position, Number(value)); | |
| } else if (this.largeBigIntToString) { | |
| return pack(value.toString()); | |
| } else if (this.useBigIntExtension || this.moreTypes) { | |
| let empty = value < 0 ? BigInt(-1) : BigInt(0); | |
| let array; | |
| if (value >> BigInt(0x10000) === empty) { | |
| let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow | |
| let chunks = []; | |
| while (true) { | |
| chunks.push(value & mask); | |
| if ((value >> BigInt(63)) === empty) break; | |
| value >>= BigInt(64); | |
| } | |
| array = new Uint8Array(new BigUint64Array(chunks).buffer); | |
| array.reverse(); | |
| } else { | |
| let invert = value < 0; | |
| let string = (invert ? ~value : value).toString(16); | |
| if (string.length % 2) { | |
| string = '0' + string; | |
| } else if (parseInt(string.charAt(0), 16) >= 8) { | |
| string = '00' + string; | |
| } | |
| if (hasNodeBuffer) { | |
| array = Buffer.from(string, 'hex'); | |
| } else { | |
| array = new Uint8Array(string.length / 2); | |
| for (let i = 0; i < array.length; i++) { | |
| array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16); | |
| } | |
| } | |
| if (invert) { | |
| for (let i = 0; i < array.length; i++) array[i] = ~array[i]; | |
| } | |
| } | |
| if (array.length + position > safeEnd) | |
| makeRoom(array.length + position); | |
| position = writeExtensionData(array, target, position, 0x42); | |
| return; | |
| } else { | |
| throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' + | |
| ' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' + | |
| ' largeBigIntToString to convert to string'); | |
| } | |
| } | |
| position += 8; | |
| } else if (type === 'undefined') { | |
| if (this.encodeUndefinedAsNil) | |
| target[position++] = 0xc0; | |
| else { | |
| target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite | |
| target[position++] = 0; | |
| target[position++] = 0; | |
| } | |
| } else { | |
| throw new Error('Unknown type: ' + type); | |
| } | |
| }; | |
| const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => { | |
| // this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects) | |
| let keys; | |
| if (this.skipValues) { | |
| keys = []; | |
| for (let key in object) { | |
| if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) && | |
| !this.skipValues.includes(object[key])) | |
| keys.push(key); | |
| } | |
| } else { | |
| keys = Object.keys(object); | |
| } | |
| let length = keys.length; | |
| if (length < 0x10) { | |
| target[position++] = 0x80 | length; | |
| } else if (length < 0x10000) { | |
| target[position++] = 0xde; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| target[position++] = 0xdf; | |
| targetView.setUint32(position, length); | |
| position += 4; | |
| } | |
| let key; | |
| if (this.coercibleKeyAsNumber) { | |
| for (let i = 0; i < length; i++) { | |
| key = keys[i]; | |
| let num = Number(key); | |
| pack(isNaN(num) ? key : num); | |
| pack(object[key]); | |
| } | |
| } else { | |
| for (let i = 0; i < length; i++) { | |
| pack(key = keys[i]); | |
| pack(object[key]); | |
| } | |
| } | |
| } : | |
| (object) => { | |
| target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards | |
| let objectOffset = position - start; | |
| position += 2; | |
| let size = 0; | |
| for (let key in object) { | |
| if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) { | |
| pack(key); | |
| pack(object[key]); | |
| size++; | |
| } | |
| } | |
| if (size > 0xffff) { | |
| throw new Error('Object is too large to serialize with fast 16-bit map size,' + | |
| ' use the "variableMapSize" option to serialize this object'); | |
| } | |
| target[objectOffset++ + start] = size >> 8; | |
| target[objectOffset + start] = size & 0xff; | |
| }; | |
| const writeRecord = this.useRecords === false ? writePlainObject : | |
| (options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written) | |
| (object) => { | |
| let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null)); | |
| let objectOffset = position++ - start; | |
| let wroteKeys; | |
| for (let key in object) { | |
| if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) { | |
| nextTransition = transition[key]; | |
| if (nextTransition) | |
| transition = nextTransition; | |
| else { | |
| // record doesn't exist, create full new record and insert it | |
| let keys = Object.keys(object); | |
| let lastTransition = transition; | |
| transition = structures.transitions; | |
| let newTransitions = 0; | |
| for (let i = 0, l = keys.length; i < l; i++) { | |
| let key = keys[i]; | |
| nextTransition = transition[key]; | |
| if (!nextTransition) { | |
| nextTransition = transition[key] = Object.create(null); | |
| newTransitions++; | |
| } | |
| transition = nextTransition; | |
| } | |
| if (objectOffset + start + 1 == position) { | |
| // first key, so we don't need to insert, we can just write record directly | |
| position--; | |
| newRecord(transition, keys, newTransitions); | |
| } else // otherwise we need to insert the record, moving existing data after the record | |
| insertNewRecord(transition, keys, objectOffset, newTransitions); | |
| wroteKeys = true; | |
| transition = lastTransition[key]; | |
| } | |
| pack(object[key]); | |
| } | |
| } | |
| if (!wroteKeys) { | |
| let recordId = transition[RECORD_SYMBOL]; | |
| if (recordId) | |
| target[objectOffset + start] = recordId; | |
| else | |
| insertNewRecord(transition, Object.keys(object), objectOffset, 0); | |
| } | |
| } : | |
| (object) => { | |
| let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null)); | |
| let newTransitions = 0; | |
| for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) { | |
| nextTransition = transition[key]; | |
| if (!nextTransition) { | |
| nextTransition = transition[key] = Object.create(null); | |
| newTransitions++; | |
| } | |
| transition = nextTransition; | |
| } | |
| let recordId = transition[RECORD_SYMBOL]; | |
| if (recordId) { | |
| if (recordId >= 0x60 && useTwoByteRecords) { | |
| target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60; | |
| target[position++] = recordId >> 5; | |
| } else | |
| target[position++] = recordId; | |
| } else { | |
| newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions); | |
| } | |
| // now write the values | |
| for (let key in object) | |
| if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) { | |
| pack(object[key]); | |
| } | |
| }; | |
| // create reference to useRecords if useRecords is a function | |
| const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords; | |
| const writeObject = checkUseRecords ? (object) => { | |
| checkUseRecords(object) ? writeRecord(object) : writePlainObject(object); | |
| } : writeRecord; | |
| const writeStruct = (object) => { | |
| let newPosition = packr._writeStruct(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => { | |
| if (notifySharedUpdate) | |
| return hasSharedUpdate = true; | |
| position = newPosition; | |
| let startTarget = target; | |
| pack(value); | |
| resetStructures(); | |
| if (startTarget !== target) { | |
| return { position, targetView, target }; // indicate the buffer was re-allocated | |
| } | |
| return position; | |
| }); | |
| if (newPosition === 0) // bail and go to a msgpack object | |
| return writeObject(object); | |
| position = newPosition; | |
| }; | |
| const makeRoom = (end) => { | |
| let newSize; | |
| if (end > 0x1000000) { | |
| // special handling for really large buffers | |
| if ((end - start) > MAX_BUFFER_SIZE) | |
| throw new Error('Packed buffer would be larger than maximum buffer size'); | |
| newSize = Math.min(MAX_BUFFER_SIZE, | |
| Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000); | |
| } else // faster handling for smaller buffers | |
| newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12; | |
| let newBuffer = new ByteArrayAllocate(newSize); | |
| targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize)); | |
| end = Math.min(end, target.length); | |
| if (target.copy) | |
| target.copy(newBuffer, 0, start, end); | |
| else | |
| newBuffer.set(target.slice(start, end)); | |
| position -= start; | |
| start = 0; | |
| safeEnd = newBuffer.length - 10; | |
| return target = newBuffer; | |
| }; | |
| const newRecord = (transition, keys, newTransitions) => { | |
| let recordId = structures.nextId; | |
| if (!recordId) | |
| recordId = 0x40; | |
| if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) { | |
| recordId = structures.nextOwnId; | |
| if (!(recordId < maxStructureId)) | |
| recordId = sharedLimitId; | |
| structures.nextOwnId = recordId + 1; | |
| } else { | |
| if (recordId >= maxStructureId)// cycle back around | |
| recordId = sharedLimitId; | |
| structures.nextId = recordId + 1; | |
| } | |
| let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1; | |
| transition[RECORD_SYMBOL] = recordId; | |
| transition.__keys__ = keys; | |
| structures[recordId - 0x40] = keys; | |
| if (recordId < sharedLimitId) { | |
| keys.isShared = true; | |
| structures.sharedLength = recordId - 0x3f; | |
| hasSharedUpdate = true; | |
| if (highByte >= 0) { | |
| target[position++] = (recordId & 0x1f) + 0x60; | |
| target[position++] = highByte; | |
| } else { | |
| target[position++] = recordId; | |
| } | |
| } else { | |
| if (highByte >= 0) { | |
| target[position++] = 0xd5; // fixext 2 | |
| target[position++] = 0x72; // "r" record defintion extension type | |
| target[position++] = (recordId & 0x1f) + 0x60; | |
| target[position++] = highByte; | |
| } else { | |
| target[position++] = 0xd4; // fixext 1 | |
| target[position++] = 0x72; // "r" record defintion extension type | |
| target[position++] = recordId; | |
| } | |
| if (newTransitions) | |
| transitionsCount += serializationsSinceTransitionRebuild * newTransitions; | |
| // record the removal of the id, we can maintain our shared structure | |
| if (recordIdsToRemove.length >= maxOwnStructures) | |
| recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones | |
| recordIdsToRemove.push(transition); | |
| pack(keys); | |
| } | |
| }; | |
| const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => { | |
| let mainTarget = target; | |
| let mainPosition = position; | |
| let mainSafeEnd = safeEnd; | |
| let mainStart = start; | |
| target = keysTarget; | |
| position = 0; | |
| start = 0; | |
| if (!target) | |
| keysTarget = target = new ByteArrayAllocate(8192); | |
| safeEnd = target.length - 10; | |
| newRecord(transition, keys, newTransitions); | |
| keysTarget = target; | |
| let keysPosition = position; | |
| target = mainTarget; | |
| position = mainPosition; | |
| safeEnd = mainSafeEnd; | |
| start = mainStart; | |
| if (keysPosition > 1) { | |
| let newEnd = position + keysPosition - 1; | |
| if (newEnd > safeEnd) | |
| makeRoom(newEnd); | |
| let insertionPosition = insertionOffset + start; | |
| target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position); | |
| target.set(keysTarget.slice(0, keysPosition), insertionPosition); | |
| position = newEnd; | |
| } else { | |
| target[insertionOffset + start] = keysTarget[0]; | |
| } | |
| }; | |
| } | |
| useBuffer(buffer) { | |
| // this means we are finished using our own buffer and we can write over it safely | |
| target = buffer; | |
| target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength)); | |
| targetView = target.dataView; | |
| position = 0; | |
| } | |
| set position (value) { | |
| position = value; | |
| } | |
| get position() { | |
| return position; | |
| } | |
| clearSharedData() { | |
| if (this.structures) | |
| this.structures = []; | |
| if (this.typedStructs) | |
| this.typedStructs = []; | |
| } | |
| }; | |
| extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ]; | |
| extensions = [{ | |
| pack(date, allocateForWrite, pack) { | |
| let seconds = date.getTime() / 1000; | |
| if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) { | |
| // Timestamp 32 | |
| let { target, targetView, position} = allocateForWrite(6); | |
| target[position++] = 0xd6; | |
| target[position++] = 0xff; | |
| targetView.setUint32(position, seconds); | |
| } else if (seconds > 0 && seconds < 0x100000000) { | |
| // Timestamp 64 | |
| let { target, targetView, position} = allocateForWrite(10); | |
| target[position++] = 0xd7; | |
| target[position++] = 0xff; | |
| targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0)); | |
| targetView.setUint32(position + 4, seconds); | |
| } else if (isNaN(seconds)) { | |
| if (this.onInvalidDate) { | |
| allocateForWrite(0); | |
| return pack(this.onInvalidDate()); | |
| } | |
| // Intentionally invalid timestamp | |
| let { target, targetView, position} = allocateForWrite(3); | |
| target[position++] = 0xd4; | |
| target[position++] = 0xff; | |
| target[position++] = 0xff; | |
| } else { | |
| // Timestamp 96 | |
| let { target, targetView, position} = allocateForWrite(15); | |
| target[position++] = 0xc7; | |
| target[position++] = 12; | |
| target[position++] = 0xff; | |
| targetView.setUint32(position, date.getMilliseconds() * 1000000); | |
| targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds))); | |
| } | |
| } | |
| }, { | |
| pack(set, allocateForWrite, pack) { | |
| if (this.setAsEmptyObject) { | |
| allocateForWrite(0); | |
| return pack({}); | |
| } | |
| let array = Array.from(set); | |
| let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0); | |
| if (this.moreTypes) { | |
| target[position++] = 0xd4; | |
| target[position++] = 0x73; // 's' for Set | |
| target[position++] = 0; | |
| } | |
| pack(array); | |
| } | |
| }, { | |
| pack(error, allocateForWrite, pack) { | |
| let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0); | |
| if (this.moreTypes) { | |
| target[position++] = 0xd4; | |
| target[position++] = 0x65; // 'e' for error | |
| target[position++] = 0; | |
| } | |
| pack([ error.name, error.message, error.cause ]); | |
| } | |
| }, { | |
| pack(regex, allocateForWrite, pack) { | |
| let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0); | |
| if (this.moreTypes) { | |
| target[position++] = 0xd4; | |
| target[position++] = 0x78; // 'x' for regeXp | |
| target[position++] = 0; | |
| } | |
| pack([ regex.source, regex.flags ]); | |
| } | |
| }, { | |
| pack(arrayBuffer, allocateForWrite) { | |
| if (this.moreTypes) | |
| writeExtBuffer(arrayBuffer, 0x10, allocateForWrite); | |
| else | |
| writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite); | |
| } | |
| }, { | |
| pack(typedArray, allocateForWrite) { | |
| let constructor = typedArray.constructor; | |
| if (constructor !== ByteArray && this.moreTypes) | |
| writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite); | |
| else | |
| writeBuffer(typedArray, allocateForWrite); | |
| } | |
| }, { | |
| pack(arrayBuffer, allocateForWrite) { | |
| if (this.moreTypes) | |
| writeExtBuffer(arrayBuffer, 0x11, allocateForWrite); | |
| else | |
| writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite); | |
| } | |
| }, { | |
| pack(c1, allocateForWrite) { // specific 0xC1 object | |
| let { target, position} = allocateForWrite(1); | |
| target[position] = 0xc1; | |
| } | |
| }]; | |
| function writeExtBuffer(typedArray, type, allocateForWrite, encode) { | |
| let length = typedArray.byteLength; | |
| if (length + 1 < 0x100) { | |
| var { target, position } = allocateForWrite(4 + length); | |
| target[position++] = 0xc7; | |
| target[position++] = length + 1; | |
| } else if (length + 1 < 0x10000) { | |
| var { target, position } = allocateForWrite(5 + length); | |
| target[position++] = 0xc8; | |
| target[position++] = (length + 1) >> 8; | |
| target[position++] = (length + 1) & 0xff; | |
| } else { | |
| var { target, position, targetView } = allocateForWrite(7 + length); | |
| target[position++] = 0xc9; | |
| targetView.setUint32(position, length + 1); // plus one for the type byte | |
| position += 4; | |
| } | |
| target[position++] = 0x74; // "t" for typed array | |
| target[position++] = type; | |
| if (!typedArray.buffer) typedArray = new Uint8Array(typedArray); | |
| target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position); | |
| } | |
| function writeBuffer(buffer, allocateForWrite) { | |
| let length = buffer.byteLength; | |
| var target, position; | |
| if (length < 0x100) { | |
| var { target, position } = allocateForWrite(length + 2); | |
| target[position++] = 0xc4; | |
| target[position++] = length; | |
| } else if (length < 0x10000) { | |
| var { target, position } = allocateForWrite(length + 3); | |
| target[position++] = 0xc5; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| var { target, position, targetView } = allocateForWrite(length + 5); | |
| target[position++] = 0xc6; | |
| targetView.setUint32(position, length); | |
| position += 4; | |
| } | |
| target.set(buffer, position); | |
| } | |
| function writeExtensionData(result, target, position, type) { | |
| let length = result.length; | |
| switch (length) { | |
| case 1: | |
| target[position++] = 0xd4; | |
| break; | |
| case 2: | |
| target[position++] = 0xd5; | |
| break; | |
| case 4: | |
| target[position++] = 0xd6; | |
| break; | |
| case 8: | |
| target[position++] = 0xd7; | |
| break; | |
| case 16: | |
| target[position++] = 0xd8; | |
| break; | |
| default: | |
| if (length < 0x100) { | |
| target[position++] = 0xc7; | |
| target[position++] = length; | |
| } else if (length < 0x10000) { | |
| target[position++] = 0xc8; | |
| target[position++] = length >> 8; | |
| target[position++] = length & 0xff; | |
| } else { | |
| target[position++] = 0xc9; | |
| target[position++] = length >> 24; | |
| target[position++] = (length >> 16) & 0xff; | |
| target[position++] = (length >> 8) & 0xff; | |
| target[position++] = length & 0xff; | |
| } | |
| } | |
| target[position++] = type; | |
| target.set(result, position); | |
| position += length; | |
| return position; | |
| } | |
| function insertIds(serialized, idsToInsert) { | |
| // insert the ids that need to be referenced for structured clones | |
| let nextId; | |
| let distanceToMove = idsToInsert.length * 6; | |
| let lastEnd = serialized.length - distanceToMove; | |
| while (nextId = idsToInsert.pop()) { | |
| let offset = nextId.offset; | |
| let id = nextId.id; | |
| serialized.copyWithin(offset + distanceToMove, offset, lastEnd); | |
| distanceToMove -= 6; | |
| let position = offset + distanceToMove; | |
| serialized[position++] = 0xd6; | |
| serialized[position++] = 0x69; // 'i' | |
| serialized[position++] = id >> 24; | |
| serialized[position++] = (id >> 16) & 0xff; | |
| serialized[position++] = (id >> 8) & 0xff; | |
| serialized[position++] = id & 0xff; | |
| lastEnd = offset; | |
| } | |
| return serialized; | |
| } | |
| function writeBundles(start, pack, incrementPosition) { | |
| if (bundledStrings.length > 0) { | |
| targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start); | |
| bundledStrings.stringsPosition = position - start; | |
| let writeStrings = bundledStrings; | |
| bundledStrings = null; | |
| pack(writeStrings[0]); | |
| pack(writeStrings[1]); | |
| } | |
| } | |
| function addExtension$1(extension) { | |
| if (extension.Class) { | |
| if (!extension.pack && !extension.write) | |
| throw new Error('Extension has no pack or write function'); | |
| if (extension.pack && !extension.type) | |
| throw new Error('Extension has no type (numeric code to identify the extension)'); | |
| extensionClasses.unshift(extension.Class); | |
| extensions.unshift(extension); | |
| } | |
| addExtension$2(extension); | |
| } | |
| function prepareStructures(structures, packr) { | |
| structures.isCompatible = (existingStructures) => { | |
| let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length); | |
| if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction | |
| packr._mergeStructures(existingStructures); | |
| return compatible; | |
| }; | |
| return structures; | |
| } | |
| // Marker for downstream libraries (e.g. structon) to detect that this Packr | |
| // supports per-instance struct-encoding hooks (this._writeStruct, | |
| // this._prepareStructures). See `pack` for the dispatch. | |
| Packr$1.SUPPORTS_STRUCT_HOOKS = true; | |
| let defaultPackr = new Packr$1({ useRecords: false }); | |
| const pack$1 = defaultPackr.pack; | |
| defaultPackr.pack; | |
| const REUSE_BUFFER_MODE = 512; | |
| const RESET_BUFFER_MODE = 1024; | |
| const RESERVE_START_SPACE = 2048; | |
| const nativeAccelerationDisabled = process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED !== undefined && process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED.toLowerCase() === 'true'; | |
| if (!nativeAccelerationDisabled) { | |
| let extractor; | |
| try { | |
| if (typeof require == 'function') | |
| extractor = require('msgpackr-extract'); | |
| else | |
| extractor = module.createRequire((document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href))('msgpackr-extract'); | |
| if (extractor) | |
| setExtractor(extractor.extractStrings); | |
| } catch (error) { | |
| // native module is optional | |
| } | |
| } | |
| let allSampleData = []; | |
| for (let i = 1; i < 6; i++) { | |
| allSampleData.push(JSON.parse(fs.readFileSync(new URL(`./example${i > 1 ? i : ''}.json`, (document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href))))); | |
| } | |
| allSampleData.push({ | |
| name: 'some other types', | |
| date: new Date(), | |
| empty: '', | |
| }); | |
| const sampleData = allSampleData[3]; | |
| function tryRequire(module) { | |
| try { | |
| return require(module); | |
| } catch(error) { | |
| return {}; | |
| } | |
| } | |
| //if (typeof chai === 'undefined') { chai = require('chai') } | |
| var assert = chai.assert; | |
| //if (typeof msgpackr === 'undefined') { msgpackr = require('..') } | |
| var Packr = Packr$1; | |
| var Unpackr = Unpackr$1; | |
| var unpack = unpack$1; | |
| var unpackMultiple = unpackMultiple$1; | |
| var roundFloat32 = roundFloat32$1; | |
| var pack = pack$1; | |
| var DECIMAL_FIT = FLOAT32_OPTIONS.DECIMAL_FIT; | |
| var addExtension = addExtension$1; | |
| var zlib = tryRequire('zlib'); | |
| zlib.deflateSync; | |
| zlib.inflateSync; | |
| zlib.brotliCompressSync; | |
| zlib.brotliDecompressSync; | |
| zlib.constants; | |
| var ITERATIONS = 4000; | |
| class ExtendArray extends Array { | |
| } | |
| class ExtendArray2 extends Array { | |
| } | |
| class ExtendArray3 extends Array { | |
| } | |
| class ExtendObject { | |
| } | |
| suite('msgpackr basic tests', function() { | |
| test('pack/unpack data', function () { | |
| var data = { | |
| data: [ | |
| {a: 1, name: 'one', type: 'odd', isOdd: true}, | |
| {a: 2, name: 'two', type: 'even'}, | |
| {a: 3, name: 'three', type: 'odd', isOdd: true}, | |
| {a: 4, name: 'four', type: 'even'}, | |
| {a: 5, name: 'five', type: 'odd', isOdd: true}, | |
| {a: 6, name: 'six', type: 'even', isOdd: null} | |
| ], | |
| description: 'some names', | |
| types: ['odd', 'even'], | |
| convertEnumToNum: [ | |
| {prop: 'test'}, | |
| {prop: 'test'}, | |
| {prop: 'test'}, | |
| {prop: 1}, | |
| {prop: 2}, | |
| {prop: [undefined]}, | |
| {prop: null} | |
| ] | |
| }; | |
| let structures = []; | |
| let packr = new Packr({structures}); | |
| var serialized = packr.pack(data); | |
| serialized = packr.pack(data); | |
| serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('mixed structures', function () { | |
| let data1 = {a: 1, b: 2, c: 3}; | |
| let data2 = {a: 1, b: 2, d: 4}; | |
| let data3 = {a: 1, b: 2, e: 5}; | |
| let structures = []; | |
| let packr = new Packr({structures}); | |
| var serialized = packr.pack(data1); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data1); | |
| var serialized = packr.pack(data2); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data2); | |
| var serialized = packr.pack(data3); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data3); | |
| }); | |
| test('mixed array', function () { | |
| var data = [ | |
| 'one', | |
| 'two', | |
| 'one', | |
| 10, | |
| 11, | |
| null, | |
| true, | |
| 'three', | |
| 'three', | |
| 'one', [ | |
| 3, -5, -50, -400, 1.3, -5.3, true | |
| ] | |
| ]; | |
| let structures = []; | |
| let packr = new Packr({structures}); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('255 chars', function () { | |
| const data = 'RRZG9A6I7xupPeOZhxcOcioFsuhszGOdyDUcbRf4Zef2kdPIfC9RaLO4jTM5JhuZvTsF09fbRHMGtqk7YAgu3vespeTe9l61ziZ6VrMnYu2CamK96wCkmz0VUXyqaiUoTPgzk414LS9yYrd5uh7w18ksJF5SlC2e91rukWvNqAZJjYN3jpkqHNOFchCwFrhbxq2Lrv1kSJPYCx9blRg2hGmYqTbElLTZHv20iNqwZeQbRMgSBPT6vnbCBPnOh1W'; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.equal(deserialized, data); | |
| }); | |
| test('overlong UTF-8 string', function () { | |
| const payload = Buffer.concat([ | |
| Buffer.from([0xa2]), // msgpack fixstr, 2 bytes | |
| Buffer.from([0xc0, 0xaf]), // overlong "/" | |
| ]); | |
| const result = unpack(payload); | |
| assert.notEqual(result, '/'); | |
| }); | |
| test('use ArrayBuffer', function () { | |
| const data = {prop: 'a test'}; | |
| var serialized = pack(data); | |
| let ab = new ArrayBuffer(serialized.length); | |
| let u8 = new Uint8Array(ab); | |
| u8.set(serialized); | |
| var deserialized = unpack(ab); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| for (let sampleData of allSampleData) { | |
| let snippet = JSON.stringify(sampleData).slice(0, 20) + '...'; | |
| test('pack/unpack sample data ' + snippet, function () { | |
| var data = sampleData; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('pack/unpack sample data with Uint8Array encoding' + snippet, function () { | |
| var data = sampleData; | |
| var serialized = pack(data); | |
| serialized = new Uint8Array(serialized); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('pack/unpack sample data with bundled strings ' + snippet, function () { | |
| var data = sampleData; | |
| let packr = new Packr({ /*structures,*/ useRecords: false, bundleStrings: true}); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| } | |
| test('pack/unpack sample data with useRecords function', function () { | |
| var data = [ | |
| {id: 1, type: 1, labels: {a: 1, b: 2}}, | |
| {id: 2, type: 1, labels: {b: 1, c: 2}}, | |
| {id: 3, type: 1, labels: {d: 1, e: 2}} | |
| ]; | |
| var alternatives = [ | |
| {useRecords: false}, // 88 bytes | |
| {useRecords: true}, // 58 bytes | |
| {mapsAsObjects: true, useRecords: (v)=>!!v.id}, // 55 bytes | |
| {mapsAsObjects: true, variableMapSize: true, useRecords: (v)=>!!v.id} // 49 bytes | |
| ]; | |
| for(let o of alternatives) { | |
| let packr = new Packr(o); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| } | |
| }); | |
| test('mapAsEmptyObject combination', function () { | |
| const msgpackr = new Packr({ useRecords: false, encodeUndefinedAsNil: true, variableMapSize: true, mapAsEmptyObject: true, setAsEmptyObject: true }); | |
| const map = new Map(); | |
| map.set('a', 1); | |
| map.set('b', 2); | |
| const set = new Set(); | |
| set.add('a'); | |
| set.add('b'); | |
| const input = { map, set }; | |
| const packed = msgpackr.pack(input); | |
| const unpacked = msgpackr.unpack(packed); | |
| assert.deepEqual(unpacked.map, {}); | |
| assert.deepEqual(unpacked.set, {}); | |
| }); | |
| test('pack/unpack numeric coercible keys', function () { | |
| var data = { a: 1, 2: 'test', '-3.45': 'test2'}; | |
| let packr = new Packr({variableMapSize: true, coercibleKeyAsNumber: true, useRecords: false}); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('pack/unpack empty data with bundled strings', function () { | |
| var data = {}; | |
| let packr = new Packr({bundleStrings: true}); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('pack/unpack large amount of chinese characters', function() { | |
| const MSGPACK_OPTIONS = {bundleStrings: true}; | |
| const item = { | |
| message: '你好你好你好你好你好你好你好你好你好', // some Chinese characters | |
| }; | |
| testSize(100); | |
| testSize(1000); | |
| testSize(10000); | |
| function testSize(size) { | |
| const list = []; | |
| for (let i = 0; i < size; i++) { | |
| list.push({...item}); | |
| } | |
| const packer = new Packr(MSGPACK_OPTIONS); | |
| const unpacker = new Unpackr(MSGPACK_OPTIONS); | |
| const encoded = packer.pack(list); | |
| const decoded = unpacker.unpack(encoded); | |
| assert.deepEqual(list, decoded); | |
| } | |
| }); | |
| test('pack/unpack sequential data', function () { | |
| var data = {foo: 1, bar: 2}; | |
| let packr = new Packr({sequential: true}); | |
| let unpackr = new Unpackr({sequential: true}); | |
| var serialized = packr.pack(data); | |
| var deserialized = unpackr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| var serialized = packr.pack(data); | |
| var deserialized = unpackr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('pack/unpack with bundled strings and sequential', function () { | |
| const options = { | |
| bundleStrings: true, | |
| sequential: true, | |
| }; | |
| const packer = new Packr(options); | |
| const unpacker = new Packr(options); | |
| const data = {data: 42}; // key length >= 4 | |
| unpacker.unpackMultiple(Buffer.concat([ | |
| packer.pack(data), | |
| packer.pack(data) | |
| ])); | |
| }); | |
| if (typeof Buffer != 'undefined') | |
| test('replace data', function(){ | |
| var data1 = { | |
| data: [ | |
| { a: 1, name: 'one', type: 'odd', isOdd: true, a: '13 characters' }, | |
| { a: 2, name: 'two', type: 'even', a: '11 characte' }, | |
| { a: 3, name: 'three', type: 'odd', isOdd: true, a: '12 character' }, | |
| { a: 4, name: 'four', type: 'even', a: '9 charact'}, | |
| { a: 5, name: 'five', type: 'odd', isOdd: true, a: '14 characters!' }, | |
| { a: 6, name: 'six', type: 'even', isOdd: null } | |
| ], | |
| }; | |
| var data2 = { | |
| data: [ | |
| { foo: 7, name: 'one', type: 'odd', isOdd: true }, | |
| { foo: 8, name: 'two', type: 'even'}, | |
| { foo: 9, name: 'three', type: 'odd', isOdd: true }, | |
| { foo: 10, name: 'four', type: 'even'}, | |
| { foo: 11, name: 'five', type: 'odd', isOdd: true }, | |
| { foo: 12, name: 'six', type: 'even', isOdd: null } | |
| ], | |
| }; | |
| var serialized1 = pack(data1); | |
| var serialized2 = pack(data2); | |
| var b = Buffer.alloc(8000); | |
| serialized1.copy(b); | |
| var deserialized1 = unpack(b, serialized1.length); | |
| serialized2.copy(b); | |
| var deserialized2 = unpack(b, serialized2.length); | |
| assert.deepEqual(deserialized1, data1); | |
| assert.deepEqual(deserialized2, data2); | |
| }); | |
| test('compact 123', function() { | |
| assert.equal(pack(123).length, 1); | |
| }); | |
| test('BigInt', function() { | |
| let packr = new Packr({ useBigIntExtension: true }); | |
| let data = { | |
| a: 3333333333333333333333333333n, | |
| b: 1234567890123456789012345678901234567890n, | |
| c: -3333333333333333333333333333n, | |
| d: -352523523642364364364264264264264264262642642n, | |
| e: 0xffffffffffffffffffffffffffn, | |
| f: -0xffffffffffffffffffffffffffn, | |
| g: (1234n << 123n) ^ (5678n << 56n) ^ 890n, | |
| h: (-1234n << 123n) ^ (5678n << 56n) ^ 890n, | |
| i: (1234n << 1234n) ^ (5678n << 567n) ^ 890n, | |
| j: (-1234n << 1234n) ^ (5678n << 567n) ^ 890n, | |
| k: 0xdeadn << 0xbeefn, | |
| l: -0xdeadn << 0xbeefn, | |
| m: 11n << 0x11111n ^ 111n, | |
| n: -11n << 0x11111n ^ 111n, | |
| o: 12345678901234567890n, | |
| p: -12345678901234567890n, | |
| exp: [], | |
| expexp: [], | |
| }; | |
| for (let n = 1n; n.toString(16).length * 4 < 1500; n <<= 1n, n |= BigInt(Math.floor(Math.random() * 2))) { | |
| data.exp.push(n, -n); | |
| } | |
| for (let n = 7n; n.toString(16).length * 4 < 150000; n *= n) { | |
| data.expexp.push(n, -n); | |
| } | |
| let serialized = packr.pack(data); | |
| let deserialized = packr.unpack(serialized); | |
| assert.deepEqual(data, deserialized); | |
| }); | |
| test('extended class pack/unpack', function(){ | |
| function Extended() { | |
| } | |
| Extended.prototype.getDouble = function() { | |
| return this.value * 2; | |
| }; | |
| var instance = new Extended(); | |
| instance.value = 4; | |
| instance.string = 'decode this: ᾜ'; | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| let packr = new Packr(); | |
| addExtension({ | |
| Class: Extended, | |
| type: 11, | |
| unpack: function(buffer) { | |
| let e = new Extended(); | |
| let data = packr.unpack(buffer); | |
| e.value = data[0]; | |
| e.string = data[1]; | |
| return e; | |
| }, | |
| pack: function(instance) { | |
| return packr.pack([instance.value, instance.string]); | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(data, deserialized); | |
| assert.equal(deserialized.extendedInstance.getDouble(), 8); | |
| }); | |
| test('extended Array class read/write', function(){ | |
| var instance = new ExtendArray(); | |
| instance.push(0); | |
| instance.push(1); | |
| instance.push(2); | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| addExtension({ | |
| Class: ExtendArray, | |
| type: 12, | |
| read: function(data) { | |
| Object.setPrototypeOf(data, ExtendArray.prototype); | |
| return data; | |
| }, | |
| write: function(instance) { | |
| return [...instance]; | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray.prototype); | |
| assert.deepEqual(data, deserialized); | |
| }); | |
| test('unregistered extended Array class read/write', function(){ | |
| var instance = new ExtendArray2(); | |
| instance.push(0); | |
| instance.push(1); | |
| instance.push(2); | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Array.prototype); | |
| assert.deepEqual(data, deserialized); | |
| }); | |
| test('unregistered extended Object class read/write', function(){ | |
| var instance = new ExtendObject(); | |
| instance.test1 = "string"; | |
| instance.test2 = 3421321; | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Object.prototype); | |
| assert.deepEqual(data, deserialized); | |
| }); | |
| test('extended class pack/unpack custom size', function(){ | |
| function TestClass() { | |
| } | |
| addExtension({ | |
| Class: TestClass, | |
| type: 0x01, | |
| pack() { | |
| return typeof Buffer != 'undefined' ? Buffer.alloc(256) : new Uint8Array(256); | |
| }, | |
| unpack(data) { | |
| return data.length; | |
| } | |
| }); | |
| let result = unpack(pack(new TestClass())); | |
| assert.equal(result, 256); | |
| }); | |
| test('extended class read/write', function(){ | |
| function Extended() { | |
| } | |
| Extended.prototype.getDouble = function() { | |
| return this.value * 2; | |
| }; | |
| var instance = new Extended(); | |
| instance.value = 4; | |
| instance.string = 'decode this: ᾜ'; | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| addExtension({ | |
| Class: Extended, | |
| type: 12, | |
| read: function(data) { | |
| let e = new Extended(); | |
| e.value = data[0]; | |
| e.string = data[1]; | |
| return e; | |
| }, | |
| write: function(instance) { | |
| return [instance.value, instance.string]; | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(data, deserialized); | |
| assert.equal(deserialized.extendedInstance.getDouble(), 8); | |
| }); | |
| test('extended class return self', function(){ | |
| function Extended() { | |
| } | |
| Extended.prototype.getDouble = function() { | |
| return this.value * 2; | |
| }; | |
| var instance = new Extended(); | |
| instance.value = 4; | |
| instance.string = 'decode this: ᾜ'; | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| addExtension({ | |
| Class: Extended, | |
| type: 13, | |
| read: function(data) { | |
| Object.setPrototypeOf(data, Extended.prototype); | |
| return data; | |
| }, | |
| write: function(data) { | |
| return data; | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(data, deserialized); | |
| assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Extended.prototype); | |
| assert.equal(deserialized.extendedInstance.getDouble(), 8); | |
| }); | |
| test('extended Array class return self', function(){ | |
| var instance = new ExtendArray3(); | |
| instance.push(0); | |
| instance.push('has multi-byte: ᾜ'); | |
| var data = { | |
| prop1: 'has multi-byte: ᾜ', | |
| extendedInstance: instance, | |
| prop2: 'more string', | |
| num: 3, | |
| }; | |
| new Packr(); | |
| addExtension({ | |
| Class: ExtendArray3, | |
| type: 14, | |
| read: function(data) { | |
| Object.setPrototypeOf(data, ExtendArray3.prototype); | |
| return data; | |
| }, | |
| write: function(data) { | |
| return data; | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(data, deserialized); | |
| assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray3.prototype); | |
| assert.equal(deserialized.extendedInstance[0], 0); | |
| }); | |
| test('extended class pack/unpack proxied', function(){ | |
| function Extended() { | |
| } | |
| Extended.prototype.__call__ = function(){ | |
| return this.value * 4; | |
| }; | |
| Extended.prototype.getDouble = function() { | |
| return this.value * 2; | |
| }; | |
| var instance = function() { instance.__call__();/* callable stuff */ }; | |
| Object.setPrototypeOf(instance,Extended.prototype); | |
| instance.value = 4; | |
| var data = instance; | |
| let packr = new Packr(); | |
| addExtension({ | |
| Class: Extended, | |
| type: 15, | |
| unpack: function(buffer) { | |
| var e = function() { e.__call__(); }; | |
| Object.setPrototypeOf(e,Extended.prototype); | |
| e.value = packr.unpack(buffer); | |
| return e; | |
| }, | |
| pack: function(instance) { | |
| return packr.pack(instance.value); | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.equal(deserialized.getDouble(), 8); | |
| }); | |
| test.skip('convert Date to string', function(){ | |
| var data = { | |
| aDate: new Date(), | |
| }; | |
| new Packr(); | |
| addExtension({ | |
| Class: Date, | |
| write(date) { | |
| return date.toString(); | |
| } | |
| }); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.equal(deserialized.aDate, data.aDate.toString()); | |
| }); | |
| test('standard pack fails on circular reference with shared structures', function () { | |
| var data = {}; | |
| data.self = data; | |
| let structures = []; | |
| let packr = new Packr({ | |
| structures, | |
| saveStructures(structures) { | |
| } | |
| }); | |
| assert.throws(function () { | |
| packr.pack(data); | |
| }); | |
| }); | |
| test('proto handling', function() { | |
| var objectWithProto = JSON.parse('{"__proto__":{"foo":3}}'); | |
| var decoded = unpack(pack(objectWithProto)); | |
| assert(!decoded.foo); | |
| var objectsWithProto = [objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto]; | |
| let packr = new Packr(); | |
| var decoded = packr.unpack(packr.pack(objectsWithProto)); | |
| for (let object of decoded) { | |
| assert(!decoded.foo); | |
| } | |
| }); | |
| test.skip('text decoder', function() { | |
| let td = new TextDecoder('ISO-8859-15'); | |
| let b = Buffer.alloc(3); | |
| for (var i = 0; i < 256; i++) { | |
| b[0] = i; | |
| b[1] = 0; | |
| b[2] = 0; | |
| let s = td.decode(b); | |
| if (!require('msgpackr-extract').isOneByte(s)) { | |
| console.log(i.toString(16), s.length); | |
| } | |
| } | |
| }); | |
| test('moreTypes: Error with causes', function() { | |
| const object = { | |
| error: new Error('test'), | |
| errorWithCause: new Error('test-1', { cause: new Error('test-2') }), | |
| type: new TypeError(), | |
| range: new RangeError('test', { cause: [1, 2] }), | |
| }; | |
| const packr = new Packr({ | |
| moreTypes: true, | |
| }); | |
| const serialized = packr.pack(object); | |
| const deserialized = packr.unpack(serialized); | |
| assert.equal(deserialized.error.message, object.error.message); | |
| assert.equal(deserialized.error.cause, object.error.cause); | |
| assert.equal(deserialized.errorWithCause.message, object.errorWithCause.message); | |
| assert.equal(deserialized.errorWithCause.cause.message, object.errorWithCause.cause.message); | |
| assert.equal(deserialized.errorWithCause.cause.cause, object.errorWithCause.cause.cause); | |
| assert.equal(deserialized.type.message, object.type.message); | |
| assert.equal(deserialized.range.message, object.range.message); | |
| assert.deepEqual(deserialized.range.cause, object.range.cause); | |
| assert(deserialized.error instanceof Error); | |
| assert(deserialized.type instanceof TypeError); | |
| assert(deserialized.range instanceof RangeError); | |
| }); | |
| test('structured cloning: self reference', function() { | |
| let object = { | |
| test: 'string', | |
| children: [ | |
| { name: 'child' } | |
| ], | |
| value: new ArrayBuffer(10) | |
| }; | |
| let u8 = new Uint8Array(object.value); | |
| u8[0] = 1; | |
| u8[1] = 2; | |
| object.self = object; | |
| object.children[1] = object; | |
| object.children[2] = object.children[0]; | |
| object.childrenAgain = object.children; | |
| let packr = new Packr({ | |
| moreTypes: true, | |
| structuredClone: true, | |
| }); | |
| var serialized = packr.pack(object); | |
| var deserialized = packr.unpack(serialized); | |
| assert.equal(deserialized.self, deserialized); | |
| assert.equal(deserialized.children[0].name, 'child'); | |
| assert.equal(deserialized.children[1], deserialized); | |
| assert.equal(deserialized.children[0], deserialized.children[2]); | |
| assert.equal(deserialized.children, deserialized.childrenAgain); | |
| assert.equal(deserialized.value.constructor.name, 'ArrayBuffer'); | |
| u8 = new Uint8Array(deserialized.value); | |
| assert.equal(u8[0], 1); | |
| assert.equal(u8[1], 2); | |
| }); | |
| test('structured cloning: self reference with more types', function() { | |
| let set = new Set(); | |
| set.add(['hello', 1, 2, { map: new Map([[set, set], ['a', 'b']]) }]); | |
| let packr = new Packr({ | |
| moreTypes: true, | |
| structuredClone: true, | |
| }); | |
| let serialized = packr.pack(set); | |
| let deserialized = packr.unpack(serialized); | |
| assert.equal(deserialized.constructor.name, 'Set'); | |
| let map = Array.from(deserialized)[0][3].map; | |
| assert.equal(map.get(deserialized), deserialized); | |
| let sizeTestMap = new Map(); | |
| for (let i = 0; i < 50; i++) { | |
| sizeTestMap.set(i || sizeTestMap, sizeTestMap); | |
| let deserialized = packr.unpack(packr.pack(sizeTestMap)); | |
| assert.equal(deserialized.size, i + 1); | |
| assert(deserialized.has(deserialized)); | |
| assert(deserialized.has(i || deserialized)); | |
| } | |
| let sizeTestSet = new Set(); | |
| for (let i = 0; i < 50; i++) { | |
| sizeTestSet.add(i || sizeTestSet); | |
| let deserialized = packr.unpack(packr.pack(sizeTestSet)); | |
| assert.equal(deserialized.size, i + 1); | |
| assert(deserialized.has(deserialized)); | |
| assert(deserialized.has(i || deserialized)); | |
| } | |
| }); | |
| test('structured cloning: types', function() { | |
| let b = typeof Buffer != 'undefined' ? Buffer.alloc(20) : new Uint8Array(20); | |
| let fa = new Float32Array(b.buffer, 8, 2); | |
| fa[0] = 2.25; | |
| fa[1] = 6; | |
| let object = { | |
| error: new Error('test'), | |
| set: new Set(['a', 'b']), | |
| regexp: /test/gi, | |
| float32Array: fa, | |
| uint16Array: new Uint16Array([3, 4]), | |
| arrayBuffer: new Uint8Array([0xde, 0xad]).buffer, | |
| dataView: new DataView(new Uint8Array([0xbe, 0xef]).buffer), | |
| }; | |
| let packr = new Packr({ | |
| moreTypes: true, | |
| structuredClone: true, | |
| }); | |
| var serialized = packr.pack(object); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(Array.from(deserialized.set), Array.from(object.set)); | |
| assert.equal(deserialized.error.message, object.error.message); | |
| assert.equal(deserialized.regexp.test('TEST'), true); | |
| assert.equal(deserialized.float32Array.constructor.name, 'Float32Array'); | |
| assert.equal(deserialized.float32Array[0], 2.25); | |
| assert.equal(deserialized.float32Array[1], 6); | |
| assert.equal(deserialized.uint16Array.constructor.name, 'Uint16Array'); | |
| assert.equal(deserialized.uint16Array[0], 3); | |
| assert.equal(deserialized.uint16Array[1], 4); | |
| assert.equal(deserialized.arrayBuffer.constructor.name, 'ArrayBuffer'); | |
| assert.equal(new DataView(deserialized.arrayBuffer).getUint16(), 0xdead); | |
| assert.equal(deserialized.dataView.constructor.name, 'DataView'); | |
| assert.equal(deserialized.dataView.getUint16(), 0xbeef); | |
| }); | |
| test('big bundledStrings', function() { | |
| const MSGPACK_OPTIONS = {bundleStrings: true}; | |
| const packer = new Packr(MSGPACK_OPTIONS); | |
| const unpacker = new Unpackr(MSGPACK_OPTIONS); | |
| const payload = { | |
| output: [ | |
| { | |
| url: 'https://www.example.com/', | |
| }, | |
| ], | |
| }; | |
| for (let i = 0; i < 10000; i++) { | |
| payload.output.push(payload.output[0]); | |
| } | |
| let deserialized = unpacker.unpack(packer.pack(payload)); | |
| assert.equal(deserialized.output[0].url, payload.output[0].url); | |
| }); | |
| test('structured clone with bundled strings', function() { | |
| const packer = new Packr({ | |
| structuredClone: true, // both options must be enabled | |
| bundleStrings: true, | |
| }); | |
| const v = {}; | |
| let shared = { | |
| name1: v, | |
| name2: v, | |
| }; | |
| let deserialized = packer.unpack(packer.pack(shared)); | |
| assert.equal(deserialized.name1, deserialized.name2); | |
| shared = {}; | |
| shared.aaaa = shared; // key length >= 4 | |
| deserialized = packer.unpack(packer.pack(shared)); | |
| assert.equal(deserialized.aaaa, deserialized); | |
| }); | |
| test('object without prototype', function(){ | |
| var data = Object.create(null); | |
| data.test = 3; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('object with __proto__', function(){ | |
| const data = { foo: 'bar', __proto__: { isAdmin: true } }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, { foo: 'bar' }); | |
| }); | |
| test('separate instances', function() { | |
| const packr = new Packr({ | |
| structures: [['m', 'e'], ['action', 'share']] | |
| }); | |
| const packr2 = new Packr({ | |
| structures: [['m', 'e'], ['action', 'share']] | |
| }); | |
| let packed = packr.pack([{m: 1, e: 2}, {action: 3, share: 4}]); | |
| // also tried directly decoding this without the first Packr instance packed = new Uint8Array([0x92, 0x40, 0x01, 0x02, 0x41, 0x03, 0x04]); | |
| console.log(packr2.unpack(packed)); | |
| }); | |
| test('many shared structures', function() { | |
| let data = []; | |
| for (let i = 0; i < 200; i++) { | |
| data.push({['a' + i]: i}); | |
| } | |
| let structures = []; | |
| let savedStructures; | |
| let packr = new Packr({ | |
| structures, | |
| saveStructures(structures) { | |
| savedStructures = structures; | |
| } | |
| }); | |
| var serializedWith32 = packr.pack(data); | |
| assert.equal(savedStructures.length, 32); | |
| var deserialized = packr.unpack(serializedWith32); | |
| assert.deepEqual(deserialized, data); | |
| structures = structures.slice(0, 32); | |
| packr = new Packr({ | |
| structures, | |
| maxSharedStructures: 100, | |
| saveStructures(structures) { | |
| savedStructures = structures; | |
| } | |
| }); | |
| deserialized = packr.unpack(serializedWith32); | |
| assert.deepEqual(deserialized, data); | |
| structures = structures.slice(0, 32); | |
| packr = new Packr({ | |
| structures, | |
| maxSharedStructures: 100, | |
| saveStructures(structures) { | |
| savedStructures = structures; | |
| } | |
| }); | |
| let serialized = packr.pack(data); | |
| assert.equal(savedStructures.length, 100); | |
| deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| deserialized = packr.unpack(serializedWith32); | |
| assert.deepEqual(deserialized, data); | |
| assert.equal(savedStructures.length, 100); | |
| deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| assert.equal(packr.structures.sharedLength, 100); | |
| }); | |
| test('more shared structures', function() { | |
| const structures = []; | |
| for (let i = 0; i < 40; i++) { | |
| structures.push(['a' + i]); | |
| } | |
| const structures2 = [...structures]; | |
| const packr = new Packr({ | |
| getStructures() { | |
| return structures; | |
| }, | |
| saveStructures(structures) { | |
| }, | |
| maxSharedStructures: 100 | |
| }); | |
| const packr2 = new Packr({ | |
| getStructures() { | |
| return structures2; | |
| }, | |
| saveStructures(structures) { | |
| }, | |
| maxSharedStructures: 100 | |
| }); | |
| const inputData = {a35: 35}; | |
| const buffer = packr.pack(inputData); | |
| const outputData = packr2.decode(buffer); | |
| assert.deepEqual(inputData, outputData); | |
| }); | |
| test('declined structure save re-packs against durable structures (no dangling record)', function() { | |
| // Regression: when saveStructures declines a save (a concurrent writer updated the shared | |
| // structures, or the store txn did not durably commit), the in-memory structures/transition | |
| // trie reference a record id that was never persisted. The re-pack must reload the durable | |
| // structures and re-mint/re-save — otherwise it re-emits the same record pointing at an | |
| // unsaved structure, and reads throw "Record id is not defined". | |
| const meta = new Packr(); | |
| let store = null; | |
| let declinedOnce = false; | |
| const packr = new Packr({ | |
| useRecords: true, | |
| getStructures() { return store ? meta.unpack(store) : undefined; }, | |
| saveStructures(structures) { | |
| if (!declinedOnce) { declinedOnce = true; return false; } // decline the first save | |
| store = meta.pack(structures); return true; | |
| }, | |
| }); | |
| const a = packr.pack({ x: 9, y: 8 }); | |
| const b = packr.pack({ x: 7, y: 6 }); | |
| const reader = new Packr({ getStructures() { return store ? meta.unpack(store) : undefined; } }); | |
| assert.deepEqual(reader.unpack(a), { x: 9, y: 8 }); | |
| assert.deepEqual(reader.unpack(b), { x: 7, y: 6 }); | |
| }); | |
| test('big buffer', function() { | |
| var size = 100000000; | |
| var data = new Uint8Array(size).fill(1); | |
| var packed = pack(data); | |
| var unpacked = unpack(packed); | |
| assert.equal(unpacked.length, size); | |
| }); | |
| test('random strings', function(){ | |
| var data = []; | |
| for (var i = 0; i < 2000; i++) { | |
| var str = 'test'; | |
| while (Math.random() < 0.7 && str.length < 0x100000) { | |
| str = str + String.fromCharCode(90/(Math.random() + 0.01)) + str; | |
| } | |
| data.push(str); | |
| } | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('map/date', function(){ | |
| var map = new Map(); | |
| map.set(4, 'four'); | |
| map.set('three', 3); | |
| var data = { | |
| map: map, | |
| date: new Date(1532219539733), | |
| farFutureDate: new Date(3532219539133), | |
| fartherFutureDate: new Date('2106-08-05T18:48:20.323Z'), | |
| ancient: new Date(-3532219539133), | |
| invalidDate: new Date('invalid') | |
| }; | |
| let packr = new Packr(); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.equal(deserialized.map.get(4), 'four'); | |
| assert.equal(deserialized.map.get('three'), 3); | |
| assert.equal(deserialized.date.getTime(), 1532219539733); | |
| assert.equal(deserialized.farFutureDate.getTime(), 3532219539133); | |
| assert.equal(deserialized.fartherFutureDate.toISOString(), '2106-08-05T18:48:20.323Z'); | |
| assert.equal(deserialized.ancient.getTime(), -3532219539133); | |
| assert.equal(deserialized.invalidDate.toString(), 'Invalid Date'); | |
| }); | |
| test('map/date with options', function(){ | |
| var map = new Map(); | |
| map.set(4, 'four'); | |
| map.set('three', 3); | |
| var data = { | |
| map: map, | |
| date: new Date(1532219539011), | |
| invalidDate: new Date('invalid') | |
| }; | |
| let packr = new Packr({ | |
| mapsAsObjects: true, | |
| useTimestamp32: true, | |
| onInvalidDate: () => 'Custom invalid date' | |
| }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.equal(deserialized.map[4], 'four'); | |
| assert.equal(deserialized.map.three, 3); | |
| assert.equal(deserialized.date.getTime(), 1532219539000); | |
| assert.equal(deserialized.invalidDate, 'Custom invalid date'); | |
| }); | |
| test('key caching', function() { | |
| var data = { | |
| foo: 2, | |
| bar: 'test', | |
| four: 4, | |
| seven: 7, | |
| foz: 3, | |
| }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| // do multiple times to test caching | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('strings', function() { | |
| var data = ['']; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| // do multiple times | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| data = 'decode this: ᾜ'; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| data = 'decode this that is longer but without any non-latin characters'; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('decimal float32', function() { | |
| var data = { | |
| a: 2.526, | |
| b: 0.0035235, | |
| c: 0.00000000000352501, | |
| d: 3252.77, | |
| }; | |
| let packr = new Packr({ | |
| useFloat32: DECIMAL_FIT | |
| }); | |
| var serialized = packr.pack(data); | |
| assert.equal(serialized.length, 32); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('int64/uint64 should be bigints by default', function() { | |
| var data = { | |
| a: 325283295382932843n | |
| }; | |
| let packr = new Packr(); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized.a, 325283295382932843n); | |
| }); | |
| test('bigint to float', function() { | |
| var data = { | |
| a: 325283295382932843n | |
| }; | |
| let packr = new Packr({ | |
| int64AsType: 'number' | |
| }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized.a, 325283295382932843); | |
| }); | |
| test('int64AsNumber compatibility', function() { | |
| // https://github.com/kriszyp/msgpackr/pull/85 | |
| var data = { | |
| a: 325283295382932843n | |
| }; | |
| let packr = new Packr({ | |
| int64AsNumber: true | |
| }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized.a, 325283295382932843); | |
| }); | |
| test('bigint to auto (float or bigint)', function() { | |
| var data = { | |
| a: -9007199254740993n, | |
| b: -9007199254740992n, | |
| c: 0n, | |
| d: 9007199254740992n, | |
| e: 9007199254740993n, | |
| }; | |
| let packr = new Packr({ | |
| int64AsType: 'auto' | |
| }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized.a, -9007199254740993n); | |
| assert.deepEqual(deserialized.b, -9007199254740992); | |
| assert.deepEqual(deserialized.c, 0); | |
| assert.deepEqual(deserialized.d, 9007199254740992); | |
| assert.deepEqual(deserialized.e, 9007199254740993n); | |
| }); | |
| test('bigint to string', function() { | |
| var data = { | |
| a: 325283295382932843n, | |
| }; | |
| let packr = new Packr({ | |
| int64AsType: 'string' | |
| }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized.a, '325283295382932843'); | |
| }); | |
| test('fixint should be one byte', function(){ | |
| let encoded = pack(123); | |
| assert.equal(encoded.length, 1); | |
| }); | |
| test('numbers', function(){ | |
| var data = { | |
| bigEncodable: 48978578104322, | |
| dateEpoch: 1530886513200, | |
| realBig: 3432235352353255323, | |
| decimal: 32.55234, | |
| negative: -34.11, | |
| exponential: 0.234e123, | |
| tiny: 3.233e-120, | |
| zero: 0, | |
| //negativeZero: -0, | |
| Infinity: Infinity | |
| }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('bigint', function(){ | |
| var data = { | |
| bigintSmall: 352n, | |
| bigintSmallNegative: -333335252n, | |
| bigintBig: 2n**64n - 1n, // biggest possible | |
| bigintBigNegative: -(2n**63n), // largest negative | |
| mixedWithNormal: 44, | |
| }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| var tooBigInt = { | |
| tooBig: 2n**66n | |
| }; | |
| assert.throws(function(){ serialized = pack(tooBigInt); }); | |
| let packr = new Packr({ | |
| largeBigIntToFloat: true | |
| }); | |
| serialized = packr.pack(tooBigInt); | |
| deserialized = unpack(serialized); | |
| assert.isTrue(deserialized.tooBig > 2n**65n); | |
| packr = new Packr({ | |
| largeBigIntToString: true | |
| }); | |
| serialized = packr.pack(tooBigInt); | |
| deserialized = unpack(serialized); | |
| assert.equal(deserialized.tooBig, (2n**66n).toString()); | |
| }); | |
| test('roundFloat32', function() { | |
| assert.equal(roundFloat32(0.00333000003), 0.00333); | |
| assert.equal(roundFloat32(43.29999999993), 43.3); | |
| }); | |
| test('buffers', function(){ | |
| var data = { | |
| buffer1: new Uint8Array([2,3,4]), | |
| buffer2: new Uint8Array(pack(sampleData)) | |
| }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('notepack test', function() { | |
| const data = { | |
| foo: 1, | |
| bar: [1, 2, 3, 4, 'abc', 'def'], | |
| foobar: { | |
| foo: true, | |
| bar: -2147483649, | |
| foobar: { | |
| foo: new Uint8Array([1, 2, 3, 4, 5]), | |
| bar: 1.5, | |
| foobar: [true, false, 'abcdefghijkmonpqrstuvwxyz'] | |
| } | |
| } | |
| }; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| var deserialized = unpack(serialized); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('arrays in map keys', function() { | |
| const msgpackr = new Packr({ mapsAsObjects: true, allowArraysInMapKeys: true }); | |
| const map = new Map(); | |
| map.set([1, 2, 3], 1); | |
| map.set([1, 2, ['foo', 3.14]], 2); | |
| const packed = msgpackr.pack(map); | |
| const unpacked = msgpackr.unpack(packed); | |
| assert.deepEqual(unpacked, { '1,2,3': 1, '1,2,foo,3.14': 2 }); | |
| }); | |
| test('utf16 causing expansion', function() { | |
| this.timeout(10000); | |
| let data = {fixstr: 'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ', str8:'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ'}; | |
| var serialized = pack(data); | |
| var deserialized = unpack(serialized); | |
| assert.deepEqual(deserialized, data); | |
| }); | |
| test('unpackMultiple', () => { | |
| let values = unpackMultiple(new Uint8Array([1, 2, 3, 4])); | |
| assert.deepEqual(values, [1, 2, 3, 4]); | |
| values = []; | |
| unpackMultiple(new Uint8Array([1, 2, 3, 4]), value => values.push(value)); | |
| assert.deepEqual(values, [1, 2, 3, 4]); | |
| }); | |
| test('unpackMultiple with positions', () => { | |
| let values = unpackMultiple(new Uint8Array([1, 2, 3, 4])); | |
| assert.deepEqual(values, [1, 2, 3, 4]); | |
| values = []; | |
| unpackMultiple(new Uint8Array([1, 2, 3, 4]), (value,start,end) => values.push([value,start,end])); | |
| assert.deepEqual(values, [[1,0,1], [2,1,2], [3,2,3], [4,3,4]]); | |
| }); | |
| test('pack toJSON returning this', () => { | |
| class Serializable { | |
| someData = [1, 2, 3, 4]; | |
| toJSON() { | |
| return this; | |
| } | |
| } | |
| const serialized = pack(new Serializable); | |
| const deserialized = unpack(serialized); | |
| assert.deepStrictEqual(deserialized, { someData: [1, 2, 3, 4] }); | |
| }); | |
| test('skip values', function () { | |
| var data = { | |
| data: [ | |
| { a: 1, name: 'one', type: 'odd', isOdd: true }, | |
| { a: 2, name: 'two', type: 'even', isOdd: undefined }, | |
| { a: 3, name: 'three', type: 'odd', isOdd: true }, | |
| { a: 4, name: 'four', type: 'even', isOdd: null}, | |
| { a: 5, name: 'five', type: 'odd', isOdd: true }, | |
| { a: 6, name: 'six', type: 'even', isOdd: null } | |
| ], | |
| description: 'some names', | |
| types: ['odd', 'even'], | |
| convertEnumToNum: [ | |
| { prop: 'test' }, | |
| { prop: 'test' }, | |
| { prop: 'test' }, | |
| { prop: 1 }, | |
| { prop: 2 }, | |
| { prop: [undefined, null] }, | |
| { prop: null } | |
| ] | |
| }; | |
| var expected = { | |
| data: [ | |
| { a: 1, name: 'one', type: 'odd', isOdd: true }, | |
| { a: 2, name: 'two', type: 'even' }, | |
| { a: 3, name: 'three', type: 'odd', isOdd: true }, | |
| { a: 4, name: 'four', type: 'even', }, | |
| { a: 5, name: 'five', type: 'odd', isOdd: true }, | |
| { a: 6, name: 'six', type: 'even' } | |
| ], | |
| description: 'some names', | |
| types: ['odd', 'even'], | |
| convertEnumToNum: [ | |
| { prop: 'test' }, | |
| { prop: 'test' }, | |
| { prop: 'test' }, | |
| { prop: 1 }, | |
| { prop: 2 }, | |
| { prop: [undefined, null] }, | |
| {} | |
| ] | |
| }; | |
| let packr = new Packr({ useRecords: false, skipValues: [undefined, null] }); | |
| var serialized = packr.pack(data); | |
| var deserialized = packr.unpack(serialized); | |
| assert.deepEqual(deserialized, expected); | |
| }); | |
| }); | |
| suite('msgpackr performance tests', function(){ | |
| test('performance JSON.parse', function() { | |
| var data = sampleData; | |
| this.timeout(10000); | |
| var serialized = JSON.stringify(data); | |
| console.log('JSON size', serialized.length); | |
| for (var i = 0; i < ITERATIONS; i++) { | |
| JSON.parse(serialized); | |
| } | |
| }); | |
| test('performance JSON.stringify', function() { | |
| var data = sampleData; | |
| this.timeout(10000); | |
| for (var i = 0; i < ITERATIONS; i++) { | |
| JSON.stringify(data); | |
| } | |
| }); | |
| test('performance unpack', function() { | |
| var data = sampleData; | |
| this.timeout(10000); | |
| let structures = []; | |
| var serialized = pack(data); | |
| console.log('MessagePack size', serialized.length); | |
| let packr = new Packr({ structures, bundleStrings: false }); | |
| var serialized = packr.pack(data); | |
| console.log('msgpackr w/ record ext size', serialized.length); | |
| for (var i = 0; i < ITERATIONS; i++) { | |
| packr.unpack(serialized); | |
| } | |
| }); | |
| test('performance pack', function() { | |
| var data = sampleData; | |
| this.timeout(10000); | |
| let structures = []; | |
| let packr = new Packr({ structures, bundleStrings: false }); | |
| let buffer = typeof Buffer != 'undefined' ? Buffer.alloc(0x10000) : new Uint8Array(0x10000); | |
| for (var i = 0; i < ITERATIONS; i++) { | |
| //serialized = pack(data, { shared: sharedStructure }) | |
| packr.useBuffer(buffer); | |
| packr.pack(data); | |
| //var serializedGzip = deflateSync(serialized) | |
| } | |
| //console.log('serialized', serialized.length, global.propertyComparisons) | |
| }); | |
| }); | |
| suite('msgpackr – two-byte record definitions (over-cap own records with a shared store)', function () { | |
| // Regression for a data-corruption bug: with a shared-structures store (getStructures/ | |
| // saveStructures) and maxOwnStructures > 32, msgpackr uses two-byte record ids | |
| // (useTwoByteRecords). Over-cap "own" record shapes fall back to the classic record encoder, | |
| // which emits a SELF-CONTAINED record definition (0xd5 0x72 <firstByte> <highByte> <keys> | |
| // <values>). On read, recordDefinition() consumed the high byte itself, then invoked | |
| // structure.read() — which, for a highByte === 0 structure, was a second-byte reader that | |
| // consumed the FIRST VALUE BYTE as a phantom high byte, mis-resolving to a never-defined id | |
| // ("Record id is not defined for N"). The one-byte path (maxOwnStructures <= 32, fixext 1 | |
| // 0xd4 0x72) was unaffected because no high byte / second-byte reader is involved. | |
| // Fix: recordDefinition reads the body via the un-wrapped reader (structure.read0). | |
| const sharedStore = () => { | |
| let saved = []; | |
| return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; }, peek: () => saved }; | |
| }; | |
| // Width-heterogeneous record generator: many distinct shapes, exceeding any modest cap, so | |
| // the over-cap classic-record fallback path is exercised heavily. | |
| const makeRecords = (count) => { | |
| const out = []; | |
| for (let i = 0; i < count; i++) { | |
| const rec = { id: i }; | |
| const n = 1 + (i % 6); | |
| for (let a = 0; a < n; a++) rec['attr_' + ((i * 7 + a) % 400)] = i % 2 ? 's' + i : i; | |
| out.push(rec); | |
| } | |
| return out; | |
| }; | |
| test('cap=256 + shared store round-trips every over-cap shape (two-byte mode)', function () { | |
| const store = sharedStore(); | |
| const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store }; | |
| const w = new Packr(o), r = new Packr(o); | |
| const records = makeRecords(3000); | |
| let sawTwoByteDef = false; | |
| for (const rec of records) { | |
| const buf = w.pack(rec); | |
| if (buf[0] === 0xd5 && buf[1] === 0x72) sawTwoByteDef = true; | |
| assert.deepEqual(r.unpack(buf), rec); | |
| } | |
| assert.ok(sawTwoByteDef, 'expected at least one two-byte (0xd5 0x72) record definition to be exercised'); | |
| }); | |
| test('over-cap own records do not grow the shared dictionary without bound', function () { | |
| const store = sharedStore(); | |
| const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store }; | |
| const w = new Packr(o); | |
| for (const rec of makeRecords(3000)) w.pack(rec); | |
| const saved = store.peek(); | |
| const named = saved instanceof Map ? (saved.get('named') || []) : (saved || []); | |
| // Over-cap shapes are inlined as self-contained definitions, not minted as ever-growing | |
| // shared ids, so the shared store stays bounded by the few genuinely shared shapes. | |
| assert.ok(named.length < 100, 'named structures should stay bounded, got ' + named.length); | |
| }); | |
| test('two-byte record REFERENCES (shared, maxShared=64) round-trip with stable shapes', function () { | |
| // maxSharedStructures > 32 forces two-byte mode; stable repeated shapes get a definition | |
| // once then are emitted as two-byte references (0x60-0x7f + high byte) — the path that | |
| // relies on structure.read remaining a second-byte reader after the fix. | |
| const store = sharedStore(); | |
| const o = { useRecords: true, maxSharedStructures: 64, maxOwnStructures: 64, ...store }; | |
| const w = new Packr(o), r = new Packr(o); | |
| const shapes = []; | |
| for (let s = 0; s < 100; s++) { const rec = {}; for (let f = 0; f <= s % 8; f++) rec['k' + s + '_' + f] = s * 10 + f; shapes.push(rec); } | |
| let sawRef = false; | |
| for (let round = 0; round < 4; round++) for (const shp of shapes) { | |
| const buf = w.pack(shp); | |
| if (buf[0] >= 0x60 && buf[0] < 0x80) sawRef = true; | |
| assert.deepEqual(r.unpack(buf), shp); | |
| } | |
| assert.ok(sawRef, 'expected at least one two-byte record reference (0x60-0x7f) to be exercised'); | |
| }); | |
| test('one-byte path (maxOwn=32) keeps working with a shared store', function () { | |
| const store = sharedStore(); | |
| const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 32, ...store }; | |
| const w = new Packr(o), r = new Packr(o); | |
| for (const rec of makeRecords(3000)) { | |
| assert.deepEqual(r.unpack(w.pack(rec)), rec); | |
| } | |
| }); | |
| }); | |
| })(chai, null, module, fs); | |
| //# sourceMappingURL=test.js.map | |
Xet Storage Details
- Size:
- 130 kB
- Xet hash:
- 9771c20e276953cd4116b6da4e8cc684ece24b7bc1452dcbb109eea2efff2c16
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.