123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104 |
- import { Unpackr, mult10, C1Type, typedArrays, addExtension as unpackAddExtension } from './unpack.js'
- 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
- let writeStructSlots
- const MAX_BUNDLE_SIZE = 0x5500 // maximum characters such that the encoded bytes fits in 16 bits.
- const hasNonLatin = /[\u0080-\uFFFF]/
- export const RECORD_SYMBOL = Symbol('record-id')
- export class Packr extends Unpackr {
- constructor(options) {
- super(options)
- this.offset = 0
- let typeBuffer
- 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.randomAccessStructure && value && value.constructor && value.constructor === Object)
- writeStruct(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 = 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) {
- // get updated structures and try again if the update failed
- 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.randomAccessStructure)) {
- 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 < (BigInt(1)<<BigInt(63)) && value >= -(BigInt(1)<<BigInt(63))) {
- // use a signed int as long as it fits
- target[position++] = 0xd3
- targetView.setBigInt64(position, value)
- } else if (value < (BigInt(1)<<BigInt(64)) && 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 && value < BigInt(2)**BigInt(1023) && value > -(BigInt(2)**BigInt(1023))) {
- target[position++] = 0xc7
- position++;
- target[position++] = 0x42 // "B" for BigInt
- let bytes = [];
- let alignedSign;
- do {
- let byte = value & BigInt(0xff);
- alignedSign = (byte & BigInt(0x80)) === (value < BigInt(0) ? BigInt(0x80) : BigInt(0));
- bytes.push(byte);
- value >>= BigInt(8);
- } while (!((value === BigInt(0) || value === BigInt(-1)) && alignedSign));
- target[position-2] = bytes.length;
- for (let i = bytes.length; i > 0;) {
- target[position++] = Number(bytes[--i]);
- }
- 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 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]
- }
- }
- const writeStruct = (object) => {
- let newPosition = writeStructSlots(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;
- }, this);
- if (newPosition === 0) // bail and go to a msgpack object
- return writeObject(object);
- position = newPosition;
- }
- }
- 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))
- 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*/, 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(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])
- }
- }
- export function addExtension(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)
- }
- unpackAddExtension(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
- }
- export function setWriteStructSlots(writeSlots, makeStructures) {
- writeStructSlots = writeSlots;
- prepareStructures = makeStructures;
- }
- let defaultPackr = new Packr({ useRecords: false })
- export const pack = defaultPackr.pack
- export const encode = defaultPackr.pack
- export const Encoder = Packr
- export { FLOAT32_OPTIONS } from './unpack.js'
- import { FLOAT32_OPTIONS } from './unpack.js'
- export const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS
- export const REUSE_BUFFER_MODE = 512
- export const RESET_BUFFER_MODE = 1024
- export const RESERVE_START_SPACE = 2048
|