@tamgl/colyseus-schema
Version:
Binary state serializer with delta encoding for games
1 lines • 411 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../src/encoding/spec.ts","../../src/symbol.shim.ts","../../src/types/symbols.ts","../../src/encoding/encode.ts","../../src/encoding/decode.ts","../../src/types/registry.ts","../../src/types/TypeContext.ts","../../src/Metadata.ts","../../src/encoder/ChangeTree.ts","../../src/encoder/EncodeOperation.ts","../../src/decoder/DecodeOperation.ts","../../src/encoding/assert.ts","../../src/types/custom/ArraySchema.ts","../../src/types/custom/MapSchema.ts","../../src/annotations.ts","../../src/utils.ts","../../src/Schema.ts","../../src/types/custom/CollectionSchema.ts","../../src/types/custom/SetSchema.ts","../../node_modules/tslib/tslib.es6.js","../../src/encoder/Root.ts","../../src/encoder/Encoder.ts","../../src/types/utils.ts","../../src/decoder/ReferenceTracker.ts","../../src/decoder/Decoder.ts","../../src/Reflection.ts","../../src/decoder/strategy/StateCallbacks.ts","../../src/decoder/strategy/RawChanges.ts","../../src/encoder/StateView.ts","../../src/index.ts"],"sourcesContent":["export const SWITCH_TO_STRUCTURE = 255; // (decoding collides with DELETE_AND_ADD + fieldIndex = 63)\r\nexport const TYPE_ID = 213;\r\n\r\n/**\r\n * Encoding Schema field operations.\r\n */\r\nexport enum OPERATION {\r\n ADD = 128, // (10000000) add new structure/primitive\r\n REPLACE = 0, // (00000001) replace structure/primitive\r\n DELETE = 64, // (01000000) delete field\r\n DELETE_AND_MOVE = 96, // () ArraySchema only\r\n MOVE_AND_ADD = 160, // () ArraySchema only\r\n DELETE_AND_ADD = 192, // (11000000) DELETE field, followed by an ADD\r\n\r\n /**\r\n * Collection operations\r\n */\r\n CLEAR = 10,\r\n\r\n /**\r\n * ArraySchema operations\r\n */\r\n REVERSE = 15,\r\n MOVE = 32,\r\n DELETE_BY_REFID = 33, // This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE\r\n ADD_BY_REFID = 129,\r\n}\r\n","\r\n//\r\n// Must have Symbol.metadata defined for metadata support on decorators:\r\n// https://github.com/microsoft/TypeScript/issues/55453#issuecomment-1687496648\r\n//\r\nexport {};\r\ndeclare global {\r\n interface SymbolConstructor {\r\n readonly metadata: unique symbol;\r\n }\r\n}\r\n(Symbol as any).metadata ??= Symbol.for(\"Symbol.metadata\");","export const $track = Symbol(\"$track\");\r\nexport const $encoder = Symbol(\"$encoder\");\r\nexport const $decoder = Symbol(\"$decoder\");\r\n\r\nexport const $filter = Symbol(\"$filter\");\r\n\r\nexport const $getByIndex = Symbol(\"$getByIndex\");\r\nexport const $deleteByIndex = Symbol(\"$deleteByIndex\");\r\n\r\n/**\r\n * Used to hold ChangeTree instances whitin the structures\r\n */\r\nexport const $changes = Symbol('$changes');\r\n\r\n/**\r\n * Used to keep track of the type of the child elements of a collection\r\n * (MapSchema, ArraySchema, etc.)\r\n */\r\nexport const $childType = Symbol('$childType');\r\n\r\n/**\r\n * Optional \"discard\" method for custom types (ArraySchema)\r\n * (Discards changes for next serialization)\r\n */\r\nexport const $onEncodeEnd = Symbol('$onEncodeEnd');\r\n\r\n/**\r\n * When decoding, this method is called after the instance is fully decoded\r\n */\r\nexport const $onDecodeEnd = Symbol(\"$onDecodeEnd\");\r\n\r\n/**\r\n * Metadata\r\n */\r\nexport const $descriptors = Symbol(\"$descriptors\");\r\nexport const $numFields = \"$__numFields\";\r\nexport const $refTypeFieldIndexes = \"$__refTypeFieldIndexes\";\r\nexport const $viewFieldIndexes = \"$__viewFieldIndexes\";\r\nexport const $fieldIndexesByViewTag = \"$__fieldIndexesByViewTag\";\r\n","/**\r\n * Copyright (c) 2018 Endel Dreyer\r\n * Copyright (c) 2014 Ion Drive Software Ltd.\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"Software\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in all\r\n * copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n * SOFTWARE\r\n */\r\n\r\nimport type { TextEncoder } from \"util\";\r\nimport type { Iterator } from \"./decode\";\r\n\r\nexport type BufferLike = number[] | ArrayBufferLike;\r\n\r\n/**\r\n * msgpack implementation highly based on notepack.io\r\n * https://github.com/darrachequesne/notepack\r\n */\r\n\r\nlet textEncoder: TextEncoder;\r\n// @ts-ignore\r\ntry { textEncoder = new TextEncoder(); } catch (e) { }\r\n\r\n// force little endian to facilitate decoding on multiple implementations\r\nconst _isLittleEndian = true; // new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1;\r\nconst _convoBuffer = new ArrayBuffer(8);\r\nconst _int32 = new Int32Array(_convoBuffer);\r\nconst _float32 = new Float32Array(_convoBuffer);\r\nconst _float64 = new Float64Array(_convoBuffer);\r\nconst _int64 = new BigInt64Array(_convoBuffer);\r\n\r\nconst hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);\r\n\r\nconst utf8Length: (str: string, _?: any) => number = (hasBufferByteLength)\r\n ? Buffer.byteLength // node\r\n : function (str: string, _?: any) {\r\n var c = 0, length = 0;\r\n for (var i = 0, l = str.length; i < l; i++) {\r\n c = str.charCodeAt(i);\r\n if (c < 0x80) {\r\n length += 1;\r\n }\r\n else if (c < 0x800) {\r\n length += 2;\r\n }\r\n else if (c < 0xd800 || c >= 0xe000) {\r\n length += 3;\r\n }\r\n else {\r\n i++;\r\n length += 4;\r\n }\r\n }\r\n return length;\r\n }\r\n\r\nfunction utf8Write(view: BufferLike, str: string, it: Iterator) {\r\n var c = 0;\r\n for (var i = 0, l = str.length; i < l; i++) {\r\n c = str.charCodeAt(i);\r\n if (c < 0x80) {\r\n view[it.offset++] = c;\r\n }\r\n else if (c < 0x800) {\r\n view[it.offset] = 0xc0 | (c >> 6);\r\n view[it.offset + 1] = 0x80 | (c & 0x3f);\r\n it.offset += 2;\r\n }\r\n else if (c < 0xd800 || c >= 0xe000) {\r\n view[it.offset] = 0xe0 | (c >> 12);\r\n view[it.offset+1] = 0x80 | (c >> 6 & 0x3f);\r\n view[it.offset+2] = 0x80 | (c & 0x3f);\r\n it.offset += 3;\r\n }\r\n else {\r\n i++;\r\n c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));\r\n view[it.offset] = 0xf0 | (c >> 18);\r\n view[it.offset+1] = 0x80 | (c >> 12 & 0x3f);\r\n view[it.offset+2] = 0x80 | (c >> 6 & 0x3f);\r\n view[it.offset+3] = 0x80 | (c & 0x3f);\r\n it.offset += 4;\r\n }\r\n }\r\n}\r\n\r\nfunction int8(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value & 255;\r\n};\r\n\r\nfunction uint8(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value & 255;\r\n};\r\n\r\nfunction int16(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value & 255;\r\n bytes[it.offset++] = (value >> 8) & 255;\r\n};\r\n\r\nfunction uint16(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value & 255;\r\n bytes[it.offset++] = (value >> 8) & 255;\r\n};\r\n\r\nfunction int32(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value & 255;\r\n bytes[it.offset++] = (value >> 8) & 255;\r\n bytes[it.offset++] = (value >> 16) & 255;\r\n bytes[it.offset++] = (value >> 24) & 255;\r\n};\r\n\r\nfunction uint32(bytes: BufferLike, value: number, it: Iterator) {\r\n const b4 = value >> 24;\r\n const b3 = value >> 16;\r\n const b2 = value >> 8;\r\n const b1 = value;\r\n bytes[it.offset++] = b1 & 255;\r\n bytes[it.offset++] = b2 & 255;\r\n bytes[it.offset++] = b3 & 255;\r\n bytes[it.offset++] = b4 & 255;\r\n};\r\n\r\nfunction int64(bytes: BufferLike, value: number, it: Iterator) {\r\n const high = Math.floor(value / Math.pow(2, 32));\r\n const low = value >>> 0;\r\n uint32(bytes, low, it);\r\n uint32(bytes, high, it);\r\n};\r\n\r\nfunction uint64(bytes: BufferLike, value: number, it: Iterator) {\r\n const high = (value / Math.pow(2, 32)) >> 0;\r\n const low = value >>> 0;\r\n uint32(bytes, low, it);\r\n uint32(bytes, high, it);\r\n};\r\n\r\nfunction bigint64(bytes: BufferLike, value: bigint, it: Iterator) {\r\n _int64[0] = BigInt.asIntN(64, value);\r\n int32(bytes, _int32[0], it);\r\n int32(bytes, _int32[1], it);\r\n}\r\n\r\nfunction biguint64(bytes: BufferLike, value: bigint, it: Iterator) {\r\n _int64[0] = BigInt.asIntN(64, value);\r\n int32(bytes, _int32[0], it);\r\n int32(bytes, _int32[1], it);\r\n}\r\n\r\nfunction float32(bytes: BufferLike, value: number, it: Iterator) {\r\n _float32[0] = value;\r\n int32(bytes, _int32[0], it);\r\n}\r\n\r\nfunction float64(bytes: BufferLike, value: number, it: Iterator) {\r\n _float64[0] = value;\r\n int32(bytes, _int32[_isLittleEndian ? 0 : 1], it);\r\n int32(bytes, _int32[_isLittleEndian ? 1 : 0], it);\r\n}\r\n\r\nfunction boolean(bytes: BufferLike, value: number, it: Iterator) {\r\n bytes[it.offset++] = value ? 1 : 0; // uint8\r\n};\r\n\r\nfunction string(bytes: BufferLike, value: string, it: Iterator) {\r\n // encode `null` strings as empty.\r\n if (!value) { value = \"\"; }\r\n\r\n let length = utf8Length(value, \"utf8\");\r\n let size = 0;\r\n\r\n // fixstr\r\n if (length < 0x20) {\r\n bytes[it.offset++] = length | 0xa0;\r\n size = 1;\r\n }\r\n // str 8\r\n else if (length < 0x100) {\r\n bytes[it.offset++] = 0xd9;\r\n bytes[it.offset++] = length % 255;\r\n size = 2;\r\n }\r\n // str 16\r\n else if (length < 0x10000) {\r\n bytes[it.offset++] = 0xda;\r\n uint16(bytes, length, it);\r\n size = 3;\r\n }\r\n // str 32\r\n else if (length < 0x100000000) {\r\n bytes[it.offset++] = 0xdb;\r\n uint32(bytes, length, it);\r\n size = 5;\r\n } else {\r\n throw new Error('String too long');\r\n }\r\n\r\n utf8Write(bytes, value, it);\r\n\r\n return size + length;\r\n}\r\n\r\nfunction number(bytes: BufferLike, value: number, it: Iterator) {\r\n if (isNaN(value)) {\r\n return number(bytes, 0, it);\r\n\r\n } else if (!isFinite(value)) {\r\n return number(bytes, (value > 0) ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER, it);\r\n\r\n } else if (value !== (value|0)) {\r\n if (Math.abs(value) <= 3.4028235e+38) { // range check\r\n _float32[0] = value;\r\n if (Math.abs(Math.abs(_float32[0]) - Math.abs(value)) < 1e-4) { // precision check; adjust 1e-n (n = precision) to in-/decrease acceptable precision loss\r\n // now we know value is in range for f32 and has acceptable precision for f32\r\n bytes[it.offset++] = 0xca;\r\n float32(bytes, value, it);\r\n return 5;\r\n }\r\n }\r\n\r\n bytes[it.offset++] = 0xcb;\r\n float64(bytes, value, it);\r\n return 9;\r\n }\r\n\r\n if (value >= 0) {\r\n // positive fixnum\r\n if (value < 0x80) {\r\n bytes[it.offset++] = value & 255; // uint8\r\n return 1;\r\n }\r\n\r\n // uint 8\r\n if (value < 0x100) {\r\n bytes[it.offset++] = 0xcc;\r\n bytes[it.offset++] = value & 255; // uint8\r\n return 2;\r\n }\r\n\r\n // uint 16\r\n if (value < 0x10000) {\r\n bytes[it.offset++] = 0xcd;\r\n uint16(bytes, value, it);\r\n return 3;\r\n }\r\n\r\n // uint 32\r\n if (value < 0x100000000) {\r\n bytes[it.offset++] = 0xce;\r\n uint32(bytes, value, it);\r\n return 5;\r\n }\r\n\r\n // uint 64\r\n bytes[it.offset++] = 0xcf;\r\n uint64(bytes, value, it);\r\n return 9;\r\n\r\n } else {\r\n\r\n // negative fixnum\r\n if (value >= -0x20) {\r\n bytes[it.offset++] = 0xe0 | (value + 0x20);\r\n return 1;\r\n }\r\n\r\n // int 8\r\n if (value >= -0x80) {\r\n bytes[it.offset++] = 0xd0;\r\n int8(bytes, value, it);\r\n return 2;\r\n }\r\n\r\n // int 16\r\n if (value >= -0x8000) {\r\n bytes[it.offset++] = 0xd1;\r\n int16(bytes, value, it);\r\n return 3;\r\n }\r\n\r\n // int 32\r\n if (value >= -0x80000000) {\r\n bytes[it.offset++] = 0xd2;\r\n int32(bytes, value, it);\r\n return 5;\r\n }\r\n\r\n // int 64\r\n bytes[it.offset++] = 0xd3;\r\n int64(bytes, value, it);\r\n return 9;\r\n }\r\n}\r\n\r\nexport const encode = {\r\n int8,\r\n uint8,\r\n int16,\r\n uint16,\r\n int32,\r\n uint32,\r\n int64,\r\n uint64,\r\n bigint64,\r\n biguint64,\r\n float32,\r\n float64,\r\n boolean,\r\n string,\r\n number,\r\n utf8Write,\r\n utf8Length,\r\n}","/**\r\n * Copyright (c) 2018 Endel Dreyer\r\n * Copyright (c) 2014 Ion Drive Software Ltd.\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"Software\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in all\r\n * copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n * SOFTWARE\r\n */\r\n\r\nimport type { BufferLike } from \"./encode\";\r\n\r\n/**\r\n * msgpack implementation highly based on notepack.io\r\n * https://github.com/darrachequesne/notepack\r\n */\r\n\r\nexport interface Iterator { offset: number; }\r\n\r\n// force little endian to facilitate decoding on multiple implementations\r\nconst _isLittleEndian = true; // new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1;\r\nconst _convoBuffer = new ArrayBuffer(8);\r\n\r\nconst _int32 = new Int32Array(_convoBuffer);\r\nconst _float32 = new Float32Array(_convoBuffer);\r\nconst _float64 = new Float64Array(_convoBuffer);\r\nconst _uint64 = new BigUint64Array(_convoBuffer);\r\nconst _int64 = new BigInt64Array(_convoBuffer);\r\n\r\nfunction utf8Read(bytes: BufferLike, it: Iterator, length: number) {\r\n var string = '', chr = 0;\r\n for (var i = it.offset, end = it.offset + length; i < end; i++) {\r\n var byte = bytes[i];\r\n if ((byte & 0x80) === 0x00) {\r\n string += String.fromCharCode(byte);\r\n continue;\r\n }\r\n if ((byte & 0xe0) === 0xc0) {\r\n string += String.fromCharCode(\r\n ((byte & 0x1f) << 6) |\r\n (bytes[++i] & 0x3f)\r\n );\r\n continue;\r\n }\r\n if ((byte & 0xf0) === 0xe0) {\r\n string += String.fromCharCode(\r\n ((byte & 0x0f) << 12) |\r\n ((bytes[++i] & 0x3f) << 6) |\r\n ((bytes[++i] & 0x3f) << 0)\r\n );\r\n continue;\r\n }\r\n if ((byte & 0xf8) === 0xf0) {\r\n chr = ((byte & 0x07) << 18) |\r\n ((bytes[++i] & 0x3f) << 12) |\r\n ((bytes[++i] & 0x3f) << 6) |\r\n ((bytes[++i] & 0x3f) << 0);\r\n if (chr >= 0x010000) { // surrogate pair\r\n chr -= 0x010000;\r\n string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);\r\n } else {\r\n string += String.fromCharCode(chr);\r\n }\r\n continue;\r\n }\r\n\r\n console.error('Invalid byte ' + byte.toString(16));\r\n // (do not throw error to avoid server/client from crashing due to hack attemps)\r\n // throw new Error('Invalid byte ' + byte.toString(16));\r\n }\r\n it.offset += length;\r\n return string;\r\n}\r\n\r\nfunction int8 (bytes: BufferLike, it: Iterator) {\r\n return uint8(bytes, it) << 24 >> 24;\r\n};\r\n\r\nfunction uint8 (bytes: BufferLike, it: Iterator) {\r\n return bytes[it.offset++];\r\n};\r\n\r\nfunction int16 (bytes: BufferLike, it: Iterator) {\r\n return uint16(bytes, it) << 16 >> 16;\r\n};\r\n\r\nfunction uint16 (bytes: BufferLike, it: Iterator) {\r\n return bytes[it.offset++] | bytes[it.offset++] << 8;\r\n};\r\n\r\nfunction int32 (bytes: BufferLike, it: Iterator) {\r\n return bytes[it.offset++] | bytes[it.offset++] << 8 | bytes[it.offset++] << 16 | bytes[it.offset++] << 24;\r\n};\r\n\r\nfunction uint32 (bytes: BufferLike, it: Iterator) {\r\n return int32(bytes, it) >>> 0;\r\n};\r\n\r\nfunction float32 (bytes: BufferLike, it: Iterator) {\r\n _int32[0] = int32(bytes, it);\r\n return _float32[0];\r\n};\r\n\r\nfunction float64 (bytes: BufferLike, it: Iterator) {\r\n _int32[_isLittleEndian ? 0 : 1] = int32(bytes, it);\r\n _int32[_isLittleEndian ? 1 : 0] = int32(bytes, it);\r\n return _float64[0];\r\n};\r\n\r\nfunction int64(bytes: BufferLike, it: Iterator) {\r\n const low = uint32(bytes, it);\r\n const high = int32(bytes, it) * Math.pow(2, 32);\r\n return high + low;\r\n};\r\n\r\nfunction uint64(bytes: BufferLike, it: Iterator) {\r\n const low = uint32(bytes, it);\r\n const high = uint32(bytes, it) * Math.pow(2, 32);\r\n return high + low;\r\n};\r\n\r\nfunction bigint64(bytes: BufferLike, it: Iterator) {\r\n _int32[0] = int32(bytes, it);\r\n _int32[1] = int32(bytes, it);\r\n return _int64[0];\r\n}\r\n\r\nfunction biguint64(bytes: BufferLike, it: Iterator) {\r\n _int32[0] = int32(bytes, it);\r\n _int32[1] = int32(bytes, it);\r\n return _uint64[0];\r\n}\r\n\r\nfunction boolean (bytes: BufferLike, it: Iterator) {\r\n return uint8(bytes, it) > 0;\r\n};\r\n\r\nfunction string (bytes: BufferLike, it: Iterator) {\r\n const prefix = bytes[it.offset++];\r\n let length: number;\r\n\r\n if (prefix < 0xc0) {\r\n // fixstr\r\n length = prefix & 0x1f;\r\n\r\n } else if (prefix === 0xd9) {\r\n length = uint8(bytes, it);\r\n\r\n } else if (prefix === 0xda) {\r\n length = uint16(bytes, it);\r\n\r\n } else if (prefix === 0xdb) {\r\n length = uint32(bytes, it);\r\n }\r\n\r\n return utf8Read(bytes, it, length);\r\n}\r\n\r\nfunction number (bytes: BufferLike, it: Iterator) {\r\n const prefix = bytes[it.offset++];\r\n\r\n if (prefix < 0x80) {\r\n // positive fixint\r\n return prefix;\r\n\r\n } else if (prefix === 0xca) {\r\n // float 32\r\n return float32(bytes, it);\r\n\r\n } else if (prefix === 0xcb) {\r\n // float 64\r\n return float64(bytes, it);\r\n\r\n } else if (prefix === 0xcc) {\r\n // uint 8\r\n return uint8(bytes, it);\r\n\r\n } else if (prefix === 0xcd) {\r\n // uint 16\r\n return uint16(bytes, it);\r\n\r\n } else if (prefix === 0xce) {\r\n // uint 32\r\n return uint32(bytes, it);\r\n\r\n } else if (prefix === 0xcf) {\r\n // uint 64\r\n return uint64(bytes, it);\r\n\r\n } else if (prefix === 0xd0) {\r\n // int 8\r\n return int8(bytes, it);\r\n\r\n } else if (prefix === 0xd1) {\r\n // int 16\r\n return int16(bytes, it);\r\n\r\n } else if (prefix === 0xd2) {\r\n // int 32\r\n return int32(bytes, it);\r\n\r\n } else if (prefix === 0xd3) {\r\n // int 64\r\n return int64(bytes, it);\r\n\r\n } else if (prefix > 0xdf) {\r\n // negative fixint\r\n return (0xff - prefix + 1) * -1\r\n }\r\n};\r\n\r\nexport function stringCheck(bytes: BufferLike, it: Iterator) {\r\n const prefix = bytes[it.offset];\r\n return (\r\n // fixstr\r\n (prefix < 0xc0 && prefix > 0xa0) ||\r\n // str 8\r\n prefix === 0xd9 ||\r\n // str 16\r\n prefix === 0xda ||\r\n // str 32\r\n prefix === 0xdb\r\n );\r\n}\r\n\r\nexport const decode = {\r\n utf8Read,\r\n int8,\r\n uint8,\r\n int16,\r\n uint16,\r\n int32,\r\n uint32,\r\n float32,\r\n float64,\r\n int64,\r\n uint64,\r\n bigint64,\r\n biguint64,\r\n boolean,\r\n string,\r\n number,\r\n stringCheck,\r\n};","import { DefinitionType, type } from \"../annotations\";\r\nimport { BufferLike, encode } from \"../encoding/encode\";\r\nimport { decode, Iterator } from \"../encoding/decode\";\r\n\r\nexport interface TypeDefinition {\r\n constructor?: any,\r\n encode?: (bytes: BufferLike, value: any, it: Iterator) => any;\r\n decode?: (bytes: BufferLike, it: Iterator) => any;\r\n}\r\n\r\nconst registeredTypes: {[identifier: string] : TypeDefinition} = {};\r\nconst identifiers = new Map<any, string>();\r\n\r\nexport function registerType(identifier: string, definition: TypeDefinition) {\r\n if (definition.constructor) {\r\n identifiers.set(definition.constructor, identifier);\r\n registeredTypes[identifier] = definition;\r\n }\r\n\r\n if (definition.encode) { encode[identifier] = definition.encode; }\r\n if (definition.decode) { decode[identifier] = definition.decode; }\r\n}\r\n\r\nexport function getIdentifier(klass: any): string {\r\n return identifiers.get(klass);\r\n}\r\n\r\nexport function getType(identifier: string): TypeDefinition {\r\n return registeredTypes[identifier];\r\n}\r\n\r\nexport function defineCustomTypes<T extends {[key: string]: TypeDefinition}>(types: T) {\r\n for (const identifier in types) {\r\n registerType(identifier, types[identifier]);\r\n }\r\n\r\n return (t: keyof T) => type(t as DefinitionType);\r\n}","import { Metadata } from \"../Metadata\";\r\nimport { Schema } from \"../Schema\";\r\nimport { $viewFieldIndexes } from \"./symbols\";\r\n\r\nexport class TypeContext {\r\n types: { [id: number]: typeof Schema; } = {};\r\n schemas = new Map<typeof Schema, number>();\r\n\r\n hasFilters: boolean = false;\r\n parentFiltered: {[typeIdAndParentIndex: string]: boolean} = {};\r\n\r\n /**\r\n * For inheritance support\r\n * Keeps track of which classes extends which. (parent -> children)\r\n */\r\n static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();\r\n static cachedContexts = new Map<typeof Schema, TypeContext>();\r\n\r\n static register(target: typeof Schema) {\r\n const parent = Object.getPrototypeOf(target);\r\n if (parent !== Schema) {\r\n let inherits = TypeContext.inheritedTypes.get(parent);\r\n if (!inherits) {\r\n inherits = new Set<typeof Schema>();\r\n TypeContext.inheritedTypes.set(parent, inherits);\r\n }\r\n inherits.add(target);\r\n }\r\n }\r\n\r\n static cache (rootClass: typeof Schema) {\r\n let context = TypeContext.cachedContexts.get(rootClass);\r\n if (!context) {\r\n context = new TypeContext(rootClass);\r\n TypeContext.cachedContexts.set(rootClass, context);\r\n }\r\n return context;\r\n }\r\n\r\n constructor(rootClass?: typeof Schema) {\r\n if (rootClass) {\r\n this.discoverTypes(rootClass);\r\n }\r\n }\r\n\r\n has(schema: typeof Schema) {\r\n return this.schemas.has(schema);\r\n }\r\n\r\n get(typeid: number) {\r\n return this.types[typeid];\r\n }\r\n\r\n add(schema: typeof Schema, typeid = this.schemas.size) {\r\n // skip if already registered\r\n if (this.schemas.has(schema)) {\r\n return false;\r\n }\r\n\r\n this.types[typeid] = schema;\r\n\r\n //\r\n // Workaround to allow using an empty Schema (with no `@type()` fields)\r\n //\r\n if (schema[Symbol.metadata] === undefined) {\r\n Metadata.initialize(schema);\r\n }\r\n\r\n this.schemas.set(schema, typeid);\r\n return true;\r\n }\r\n\r\n getTypeId(klass: typeof Schema) {\r\n return this.schemas.get(klass);\r\n }\r\n\r\n private discoverTypes(klass: typeof Schema, parentType?: typeof Schema, parentIndex?: number, parentHasViewTag?: boolean) {\r\n if (parentHasViewTag) {\r\n this.registerFilteredByParent(klass, parentType, parentIndex);\r\n }\r\n\r\n // skip if already registered\r\n if (!this.add(klass)) { return; }\r\n\r\n // add classes inherited from this base class\r\n TypeContext.inheritedTypes.get(klass)?.forEach((child) => {\r\n this.discoverTypes(child, parentType, parentIndex, parentHasViewTag);\r\n });\r\n\r\n // add parent classes\r\n let parent: any = klass;\r\n while (\r\n (parent = Object.getPrototypeOf(parent)) &&\r\n parent !== Schema && // stop at root (Schema)\r\n parent !== Function.prototype // stop at root (non-Schema)\r\n ) {\r\n this.discoverTypes(parent);\r\n }\r\n\r\n const metadata: Metadata = (klass[Symbol.metadata] ??= {});\r\n\r\n // if any schema/field has filters, mark \"context\" as having filters.\r\n if (metadata[$viewFieldIndexes]) {\r\n this.hasFilters = true;\r\n }\r\n\r\n for (const fieldIndex in metadata) {\r\n const index = fieldIndex as any as number;\r\n\r\n const fieldType = metadata[index].type;\r\n const fieldHasViewTag = (metadata[index].tag !== undefined);\r\n\r\n if (typeof (fieldType) === \"string\") {\r\n continue;\r\n }\r\n\r\n if (Array.isArray(fieldType)) {\r\n const type = fieldType[0];\r\n\r\n // skip primitive types\r\n if (type === \"string\") {\r\n continue;\r\n }\r\n\r\n this.discoverTypes(type as typeof Schema, klass, index, parentHasViewTag || fieldHasViewTag);\r\n\r\n } else if (typeof (fieldType) === \"function\") {\r\n this.discoverTypes(fieldType as typeof Schema, klass, index, parentHasViewTag || fieldHasViewTag);\r\n\r\n } else {\r\n const type = Object.values(fieldType)[0];\r\n\r\n // skip primitive types\r\n if (typeof (type) === \"string\") {\r\n continue;\r\n }\r\n\r\n this.discoverTypes(type as typeof Schema, klass, index, parentHasViewTag || fieldHasViewTag);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Keep track of which classes have filters applied.\r\n * Format: `${typeid}-${parentTypeid}-${parentIndex}`\r\n */\r\n private registerFilteredByParent(schema: typeof Schema, parentType?: typeof Schema, parentIndex?: number) {\r\n const typeid = this.schemas.get(schema) ?? this.schemas.size;\r\n\r\n let key = `${typeid}`;\r\n if (parentType) { key += `-${this.schemas.get(parentType)}`; }\r\n\r\n key += `-${parentIndex}`;\r\n this.parentFiltered[key] = true;\r\n }\r\n\r\n debug() {\r\n let parentFiltered = \"\";\r\n\r\n for (const key in this.parentFiltered) {\r\n const keys: number[] = key.split(\"-\").map(Number);\r\n const fieldIndex = keys.pop();\r\n\r\n parentFiltered += `\\n\\t\\t`;\r\n parentFiltered += `${key}: ${keys.reverse().map((id, i) => {\r\n const klass = this.types[id];\r\n const metadata: Metadata = klass[Symbol.metadata];\r\n let txt = klass.name;\r\n if (i === 0) { txt += `[${metadata[fieldIndex].name}]`; }\r\n return `${txt}`;\r\n }).join(\" -> \")}`;\r\n }\r\n\r\n return `TypeContext ->\\n` +\r\n `\\tSchema types: ${this.schemas.size}\\n` +\r\n `\\thasFilters: ${this.hasFilters}\\n` +\r\n `\\tparentFiltered:${parentFiltered}`;\r\n }\r\n\r\n}\r\n","import { DefinitionType, getPropertyDescriptor } from \"./annotations\";\r\nimport { Schema } from \"./Schema\";\r\nimport { getType } from \"./types/registry\";\r\nimport { $decoder, $descriptors, $encoder, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $track, $viewFieldIndexes } from \"./types/symbols\";\r\nimport { TypeContext } from \"./types/TypeContext\";\r\n\r\nexport type MetadataField = {\r\n type: DefinitionType,\r\n name: string,\r\n index: number,\r\n tag?: number,\r\n unreliable?: boolean,\r\n deprecated?: boolean,\r\n};\r\n\r\nexport type Metadata =\r\n { [$numFields]: number; } & // number of fields\r\n { [$viewFieldIndexes]: number[]; } & // all field indexes with \"view\" tag\r\n { [$fieldIndexesByViewTag]: {[tag: number]: number[]}; } & // field indexes by \"view\" tag\r\n { [$refTypeFieldIndexes]: number[]; } & // all field indexes containing Ref types (Schema, ArraySchema, MapSchema, etc)\r\n { [field: number]: MetadataField; } & // index => field name\r\n { [field: string]: number; } & // field name => field metadata\r\n { [$descriptors]: { [field: string]: PropertyDescriptor } } // property descriptors\r\n\r\nexport function getNormalizedType(type: DefinitionType): DefinitionType {\r\n return (Array.isArray(type))\r\n ? { array: type[0] }\r\n : (typeof(type['type']) !== \"undefined\")\r\n ? type['type']\r\n : type;\r\n}\r\n\r\n// TODO: see test: \"should support TypeScript enums\"\r\nfunction isTSEnum(_enum: any) {\r\n const keys = Object.keys(_enum);\r\n const numericFields = keys.filter(k => /\\d+/.test(k));\r\n return (numericFields.length === (keys.length / 2) && _enum[_enum[numericFields[0]]] == numericFields[0]);\r\n}\r\n\r\nexport const Metadata = {\r\n\r\n addField(metadata: any, index: number, name: string, type: DefinitionType, descriptor?: PropertyDescriptor) {\r\n if (index > 64) {\r\n throw new Error(`Can't define field '${name}'.\\nSchema instances may only have up to 64 fields.`);\r\n }\r\n\r\n metadata[index] = Object.assign(\r\n metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)\r\n {\r\n type: getNormalizedType(type),\r\n index,\r\n name,\r\n }\r\n );\r\n\r\n // create \"descriptors\" map\r\n Object.defineProperty(metadata, $descriptors, {\r\n value: metadata[$descriptors] || {},\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n\r\n if (descriptor) {\r\n // for encoder\r\n metadata[$descriptors][name] = descriptor;\r\n metadata[$descriptors][`_${name}`] = {\r\n value: undefined,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n };\r\n } else {\r\n // for decoder\r\n metadata[$descriptors][name] = {\r\n value: undefined,\r\n writable: true,\r\n enumerable: true,\r\n configurable: true,\r\n };\r\n }\r\n\r\n // map -1 as last field index\r\n Object.defineProperty(metadata, $numFields, {\r\n value: index,\r\n enumerable: false,\r\n configurable: true\r\n });\r\n\r\n // map field name => index (non enumerable)\r\n Object.defineProperty(metadata, name, {\r\n value: index,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n\r\n // if child Ref/complex type, add to -4\r\n if (typeof (metadata[index].type) !== \"string\") {\r\n if (metadata[$refTypeFieldIndexes] === undefined) {\r\n Object.defineProperty(metadata, $refTypeFieldIndexes, {\r\n value: [],\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n }\r\n metadata[$refTypeFieldIndexes].push(index);\r\n }\r\n },\r\n\r\n setTag(metadata: Metadata, fieldName: string, tag: number) {\r\n const index = metadata[fieldName];\r\n const field = metadata[index];\r\n\r\n // add 'tag' to the field\r\n field.tag = tag;\r\n\r\n if (!metadata[$viewFieldIndexes]) {\r\n // -2: all field indexes with \"view\" tag\r\n Object.defineProperty(metadata, $viewFieldIndexes, {\r\n value: [],\r\n enumerable: false,\r\n configurable: true\r\n });\r\n\r\n // -3: field indexes by \"view\" tag\r\n Object.defineProperty(metadata, $fieldIndexesByViewTag, {\r\n value: {},\r\n enumerable: false,\r\n configurable: true\r\n });\r\n }\r\n\r\n metadata[$viewFieldIndexes].push(index);\r\n\r\n if (!metadata[$fieldIndexesByViewTag][tag]) {\r\n metadata[$fieldIndexesByViewTag][tag] = [];\r\n }\r\n\r\n metadata[$fieldIndexesByViewTag][tag].push(index);\r\n },\r\n\r\n setFields<T extends { new (...args: any[]): InstanceType<T> } = any>(target: T, fields: { [field in keyof InstanceType<T>]?: DefinitionType }) {\r\n // for inheritance support\r\n const constructor = target.prototype.constructor;\r\n TypeContext.register(constructor);\r\n\r\n const parentClass = Object.getPrototypeOf(constructor);\r\n const parentMetadata = parentClass && parentClass[Symbol.metadata];\r\n const metadata = Metadata.initialize(constructor);\r\n\r\n // Use Schema's methods if not defined in the class\r\n if (!constructor[$track]) { constructor[$track] = Schema[$track]; }\r\n if (!constructor[$encoder]) { constructor[$encoder] = Schema[$encoder]; }\r\n if (!constructor[$decoder]) { constructor[$decoder] = Schema[$decoder]; }\r\n if (!constructor.prototype.toJSON) { constructor.prototype.toJSON = Schema.prototype.toJSON; }\r\n\r\n //\r\n // detect index for this field, considering inheritance\r\n //\r\n let fieldIndex = metadata[$numFields] // current structure already has fields defined\r\n ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined\r\n ?? -1; // no fields defined\r\n\r\n fieldIndex++;\r\n\r\n for (const field in fields) {\r\n const type = fields[field];\r\n\r\n // FIXME: this code is duplicated from @type() annotation\r\n const complexTypeKlass = (Array.isArray(type))\r\n ? getType(\"array\")\r\n : (typeof(Object.keys(type)[0]) === \"string\") && getType(Object.keys(type)[0]);\r\n\r\n const childType = (complexTypeKlass)\r\n ? Object.values(type)[0]\r\n : getNormalizedType(type);\r\n\r\n Metadata.addField(\r\n metadata,\r\n fieldIndex,\r\n field,\r\n type,\r\n getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass)\r\n );\r\n\r\n fieldIndex++;\r\n }\r\n\r\n return target;\r\n },\r\n\r\n isDeprecated(metadata: any, field: string) {\r\n return metadata[field].deprecated === true;\r\n },\r\n\r\n init(klass: any) {\r\n //\r\n // Used only to initialize an empty Schema (Encoder#constructor)\r\n // TODO: remove/refactor this...\r\n //\r\n const metadata = {};\r\n klass[Symbol.metadata] = metadata;\r\n Object.defineProperty(metadata, $numFields, {\r\n value: 0,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n },\r\n\r\n initialize(constructor: any) {\r\n const parentClass = Object.getPrototypeOf(constructor);\r\n const parentMetadata: Metadata = parentClass[Symbol.metadata];\r\n\r\n let metadata: Metadata = constructor[Symbol.metadata] ?? Object.create(null);\r\n\r\n // make sure inherited classes have their own metadata object.\r\n if (parentClass !== Schema && metadata === parentMetadata) {\r\n metadata = Object.create(null);\r\n\r\n if (parentMetadata) {\r\n //\r\n // assign parent metadata to current\r\n //\r\n Object.setPrototypeOf(metadata, parentMetadata);\r\n\r\n // $numFields\r\n Object.defineProperty(metadata, $numFields, {\r\n value: parentMetadata[$numFields],\r\n enumerable: false,\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n // $viewFieldIndexes / $fieldIndexesByViewTag\r\n if (parentMetadata[$viewFieldIndexes] !== undefined) {\r\n Object.defineProperty(metadata, $viewFieldIndexes, {\r\n value: [...parentMetadata[$viewFieldIndexes]],\r\n enumerable: false,\r\n configurable: true,\r\n writable: true,\r\n });\r\n Object.defineProperty(metadata, $fieldIndexesByViewTag, {\r\n value: { ...parentMetadata[$fieldIndexesByViewTag] },\r\n enumerable: false,\r\n configurable: true,\r\n writable: true,\r\n });\r\n }\r\n\r\n // $refTypeFieldIndexes\r\n if (parentMetadata[$refTypeFieldIndexes] !== undefined) {\r\n Object.defineProperty(metadata, $refTypeFieldIndexes, {\r\n value: [...parentMetadata[$refTypeFieldIndexes]],\r\n enumerable: false,\r\n configurable: true,\r\n writable: true,\r\n });\r\n }\r\n\r\n // $descriptors\r\n Object.defineProperty(metadata, $descriptors, {\r\n value: { ...parentMetadata[$descriptors] },\r\n enumerable: false,\r\n configurable: true,\r\n writable: true,\r\n });\r\n }\r\n }\r\n\r\n constructor[Symbol.metadata] = metadata;\r\n\r\n return metadata;\r\n },\r\n\r\n isValidInstance(klass: any) {\r\n return (\r\n klass.constructor[Symbol.metadata] &&\r\n Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields) as boolean\r\n );\r\n },\r\n\r\n getFields(klass: any) {\r\n const metadata: Metadata = klass[Symbol.metadata];\r\n const fields = {};\r\n for (let i = 0; i <= metadata[$numFields]; i++) {\r\n fields[metadata[i].name] = metadata[i].type;\r\n }\r\n return fields;\r\n },\r\n\r\n hasViewTagAtIndex(metadata: Metadata, index: number) {\r\n return metadata?.[$viewFieldIndexes]?.includes(index);\r\n }\r\n}","import { OPERATION } from \"../encoding/spec\";\r\nimport { Schema } from \"../Schema\";\r\nimport { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $refTypeFieldIndexes, $viewFieldIndexes } from \"../types/symbols\";\r\n\r\nimport type { MapSchema } from \"../types/custom/MapSchema\";\r\nimport type { ArraySchema } from \"../types/custom/ArraySchema\";\r\nimport type { CollectionSchema } from \"../types/custom/CollectionSchema\";\r\nimport type { SetSchema } from \"../types/custom/SetSchema\";\r\n\r\nimport { Root } from \"./Root\";\r\nimport { Metadata } from \"../Metadata\";\r\nimport type { EncodeOperation } from \"./EncodeOperation\";\r\nimport type { DecodeOperation } from \"../decoder/DecodeOperation\";\r\n\r\ndeclare global {\r\n interface Object {\r\n // FIXME: not a good practice to extend globals here\r\n [$changes]?: ChangeTree;\r\n [$encoder]?: EncodeOperation,\r\n [$decoder]?: DecodeOperation,\r\n }\r\n}\r\n\r\nexport type Ref = Schema\r\n | ArraySchema\r\n | MapSchema\r\n | CollectionSchema\r\n | SetSchema;\r\n\r\nexport type ChangeSetName = \"changes\"\r\n | \"allChanges\"\r\n | \"filteredChanges\"\r\n | \"allFilteredChanges\";\r\n\r\nexport interface IndexedOperations {\r\n [index: number]: OPERATION;\r\n}\r\n\r\nexport interface ChangeSet {\r\n // field index -> operation index\r\n indexes: { [index: number]: number };\r\n operations: number[];\r\n queueRootIndex?: number; // index of ChangeTree structure in `root.changes` or `root.filteredChanges`\r\n}\r\n\r\nfunction createChangeSet(): ChangeSet {\r\n return { indexes: {}, operations: [] };\r\n}\r\n\r\nexport function setOperationAtIndex(changeSet: ChangeSet, index: number) {\r\n const operationsIndex = changeSet.indexes[index];\r\n if (operationsIndex === undefined) {\r\n changeSet.indexes[index] = changeSet.operations.push(index) - 1;\r\n } else {\r\n changeSet.operations[operationsIndex] = index;\r\n }\r\n}\r\n\r\nexport function deleteOperationAtIndex(changeSet: ChangeSet, index: number | string) {\r\n let operationsIndex = changeSet.indexes[index];\r\n if (operationsIndex === undefined) {\r\n //\r\n // if index is not found, we need to find the last operation\r\n // FIXME: this is not very efficient\r\n //\r\n // > See \"should allow consecutive splices (same place)\" tests\r\n //\r\n operationsIndex = Object.values(changeSet.indexes).at(-1);\r\n index = Object.entries(changeSet.indexes).find(([_, value]) => value === operationsIndex)?.[0];\r\n }\r\n changeSet.operations[operationsIndex] = undefined;\r\n delete changeSet.indexes[index];\r\n}\r\n\r\nexport function debugChangeSet(label: string, changeSet: ChangeSet) {\r\n let indexes: string[] = [];\r\n let operations: string[] = [];\r\n\r\n for (const index in changeSet.indexes) {\r\n indexes.push(`\\t${index} => [${changeSet.indexes[index]}]`);\r\n }\r\n\r\n for (let i = 0; i < changeSet.operations.length; i++) {\r\n const index = changeSet.operations[i];\r\n if (index !== undefined) {\r\n operations.push(`\\t[${i}] => ${index}`);\r\n }\r\n }\r\n\r\n console.log(`${label} =>\\nindexes (${Object.keys(changeSet.indexes).length}) {`);\r\n console.log(indexes.join(\"\\n\"), \"\\n}\");\r\n console.log(`operations (${changeSet.operations.filter(op => op !== undefined).length}) {`);\r\n console.log(operations.join(\"\\n\"), \"\\n}\");\r\n}\r\n\r\nexport function enqueueChangeTree(\r\n root: Root,\r\n changeTree: ChangeTree,\r\n changeSet: 'changes' | 'filteredChanges' | 'allFilteredChanges',\r\n queueRootIndex = changeTree[changeSet].queueRootIndex\r\n) {\r\n if (!root) {\r\n // skip\r\n return;\r\n\r\n } else if (root[changeSet][queueRootIndex] !== changeTree) {\r\n changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;\r\n }\r\n}\r\n\r\nexport class ChangeTree<T extends Ref=any> {\r\n ref: T;\r\n refId: number;\r\n\r\n root?: Root;\r\n parent?: Ref;\r\n parentIndex?: number;\r\n\r\n /**\r\n * Whether this structure is parent of a filtered structure.\r\n */\r\n isFiltered: boolean = false;\r\n isVisibilitySharedWithParent?: boolean; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'\r\n\r\n indexedOperations: IndexedOperations = {};\r\n\r\n //\r\n // TODO:\r\n // try storing the index + operation per item.\r\n // example: 1024 & 1025 => ADD, 1026 => DELETE\r\n //\r\n // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196\r\n //\r\n changes: ChangeSet = { indexes: {}, operations: [] };\r\n allChanges: ChangeSet = { indexes: {}, operations: [] };\r\n filteredChanges: ChangeSet;\r\n allFilteredChanges: ChangeSet;\r\n\r\n indexes: {[index: string]: any}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)\r\n\r\n /**\r\n * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.\r\n */\r\n isNew = true;\r\n\r\n constructor(ref: T) {\r\n this.ref = ref;\r\n\r\n //\r\n // Does this structure have \"filters\" declared?\r\n //\r\n const metadata = ref.constructor[Symbol.metadata];\r\n if (metadata?.[$viewFieldIndexes]) {\r\n this.allFilteredChanges = { indexes: {}, operations: [] };\r\n this.filteredChanges = { indexes: {}, operations: [] };\r\n }\r\n }\r\n\r\n setRoot(root: Root) {\r\n this.root = root;\r\n this.checkIsFiltered(this.parent, this.parentIndex);\r\n\r\n //\r\n // TODO: refactor and possibly unify .setRoot() and .setParent()\r\n //\r\n\r\n // Recursively set root on child structures\r\n const metadata: Metadata = this.ref.constructor[Symbol.metadata];\r\n if (metadata) {\r\n metadata[$refTypeFieldIndexes]?.forEach((index) => {\r\n const field = metadata[index as any as number];\r\n const changeTree: ChangeTree = this.ref[field.name]?.[$changes];\r\n if (changeTree) {\r\n if (changeTree.root !== root) {\r\n changeTree.setRoot(root);\r\n } else {\r\n root.add(changeTree); // increment refCount\r\n }\r\n }\r\n });\r\n\r\n } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== \"string\") {\r\n // MapSchema / ArraySchema, etc.\r\n (this.ref as MapSchema).forEach((value, key) => {\r\n const changeTree: ChangeTree = value[$changes];\r\n if (changeTree.root !== root) {\r\n changeTree.setRoot(root);\r\n } else {\r\n root.add(changeTree); // increment refCount\r\n }\r\n });\r\n }\r\n }\r\n\r\n setParent(\r\n parent: Ref,\r\n root?: Root,\r\n parentIndex?: number,\r\n ) {\r\n this.parent = parent;\r\n this.parentIndex = parentIndex;\r\n\r\n // avoid setting parents with empty `root`\r\n if (!root) { return; }\r\n\r\n // skip if parent is already set\r\n if (root !== this.root) {\r\n this.root = root;\r\n this.checkIsFiltered(parent, parentIndex);\r\n\r\n } else {\r\n root.add(this);\r\n }\r\n\r\n // assign same parent on child structures\r\n const metadata: Metadata = this.ref.constructor[Symbol.metadata];\r\n if (metadata) {\r\n metadata[$refTypeFieldIndexes]?.forEach((index) => {\r\n const field = metadata[index as any as number];\r\n const changeTree: ChangeTree = this.ref[field.name]?.[$changes];\r\n if (changeTree && changeTree.root !== root) {\r\n changeTree.setParent(this.ref, root, index);\r\n }\r\n });\r\n\r\n } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== \"string\") {\r\n // MapSchema / ArraySchema, etc.\r\n (this.ref as MapSchema).forEach((value, key) => {\r\n const changeTree: ChangeTree = value[$changes];\r\n if (changeTree.root !== root) {\r\n changeTree.setParent(this.ref, root, this.indexes[key] ?? key);\r\n }\r\n });\r\n }\r\n\r\n }\r\n\r\n forEachChild(callback: (change: ChangeTree, atIndex: number) => void) {\r\n //\r\n // assign same parent on child structures\r\n //\r\n const metadata: Metadata = this.ref.constructor[Symbol.metadata];\r\n if (metadata) {\r\n metadata[$refTypeFieldIndexes]?.forEach((index) => {\r\n const field = metadata[index as any as number];\r\n const value = this.ref[field.name];\r\n if (value) {\r\n callback(value[$changes], index);\r\n }\r\n });\r\n\r\n } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== \"string\") {\r\n // MapSchema / ArraySchema, etc.\r\n (this.ref as MapSchema).forEach((value, key) => {\r\n callback(value[$changes], this.indexes[key] ?? key);\r\n });\r\n }\r\n }\r\n\r\n operation(op: OPERATION) {\r\n // operations without index use negative values to represent them\r\n // this is checked during .encode() time.\r\n if (this.filteredChanges !==