@haneullabs/bcs 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"reader.mjs","names":[],"sources":["../src/reader.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ulebDecode } from './uleb.js';\n\n/**\n * Class used for reading BCS data chunk by chunk. Meant to be used\n * by some wrapper, which will make sure that data is valid and is\n * matching the desired format.\n *\n * @example\n * // data for this example is:\n * // { a: u8, b: u32, c: bool, d: u64 }\n *\n * let reader = new BcsReader(\"647f1a060001ffffe7890423c78a050102030405\");\n * let field1 = reader.read8();\n * let field2 = reader.read32();\n * let field3 = reader.read8() === '1'; // bool\n * let field4 = reader.read64();\n * // ....\n *\n * Reading vectors is another deal in bcs. To read a vector, you first need to read\n * its length using {@link readULEB}. Here's an example:\n * @example\n * // data encoded: { field: [1, 2, 3, 4, 5] }\n * let reader = new BcsReader(\"050102030405\");\n * let vec_length = reader.readULEB();\n * let elements = [];\n * for (let i = 0; i < vec_length; i++) {\n * elements.push(reader.read8());\n * }\n * console.log(elements); // [1,2,3,4,5]\n *\n * @param {String} data HEX-encoded data (serialized BCS)\n */\nexport class BcsReader {\n\tprivate dataView: DataView;\n\tprivate bytePosition: number = 0;\n\n\t/**\n\t * @param {Uint8Array} data Data to use as a buffer.\n\t */\n\tconstructor(data: Uint8Array) {\n\t\tthis.dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\t}\n\t/**\n\t * Shift current cursor position by `bytes`.\n\t *\n\t * @param {Number} bytes Number of bytes to\n\t * @returns {this} Self for possible chaining.\n\t */\n\tshift(bytes: number) {\n\t\tthis.bytePosition += bytes;\n\t\treturn this;\n\t}\n\t/**\n\t * Read U8 value from the buffer and shift cursor by 1.\n\t * @returns\n\t */\n\tread8(): number {\n\t\tconst value = this.dataView.getUint8(this.bytePosition);\n\t\tthis.shift(1);\n\t\treturn value;\n\t}\n\t/**\n\t * Read U16 value from the buffer and shift cursor by 2.\n\t * @returns\n\t */\n\tread16(): number {\n\t\tconst value = this.dataView.getUint16(this.bytePosition, true);\n\t\tthis.shift(2);\n\t\treturn value;\n\t}\n\t/**\n\t * Read U32 value from the buffer and shift cursor by 4.\n\t * @returns\n\t */\n\tread32(): number {\n\t\tconst value = this.dataView.getUint32(this.bytePosition, true);\n\t\tthis.shift(4);\n\t\treturn value;\n\t}\n\t/**\n\t * Read U64 value from the buffer and shift cursor by 8.\n\t * @returns\n\t */\n\tread64(): string {\n\t\tconst value1 = this.read32();\n\t\tconst value2 = this.read32();\n\n\t\tconst result = value2.toString(16) + value1.toString(16).padStart(8, '0');\n\n\t\treturn BigInt('0x' + result).toString(10);\n\t}\n\t/**\n\t * Read U128 value from the buffer and shift cursor by 16.\n\t */\n\tread128(): string {\n\t\tconst value1 = BigInt(this.read64());\n\t\tconst value2 = BigInt(this.read64());\n\t\tconst result = value2.toString(16) + value1.toString(16).padStart(16, '0');\n\n\t\treturn BigInt('0x' + result).toString(10);\n\t}\n\t/**\n\t * Read U128 value from the buffer and shift cursor by 32.\n\t * @returns\n\t */\n\tread256(): string {\n\t\tconst value1 = BigInt(this.read128());\n\t\tconst value2 = BigInt(this.read128());\n\t\tconst result = value2.toString(16) + value1.toString(16).padStart(32, '0');\n\n\t\treturn BigInt('0x' + result).toString(10);\n\t}\n\t/**\n\t * Read `num` number of bytes from the buffer and shift cursor by `num`.\n\t * @param num Number of bytes to read.\n\t */\n\treadBytes(num: number): Uint8Array {\n\t\tconst start = this.bytePosition + this.dataView.byteOffset;\n\t\tconst value = new Uint8Array(this.dataView.buffer, start, num);\n\n\t\tthis.shift(num);\n\n\t\treturn value;\n\t}\n\t/**\n\t * Read ULEB value - an integer of varying size. Used for enum indexes and\n\t * vector lengths.\n\t * @returns {Number} The ULEB value.\n\t */\n\treadULEB(): number {\n\t\tconst start = this.bytePosition + this.dataView.byteOffset;\n\t\tconst buffer = new Uint8Array(this.dataView.buffer, start);\n\t\tconst { value, length } = ulebDecode(buffer);\n\n\t\tthis.shift(length);\n\n\t\treturn value;\n\t}\n\t/**\n\t * Read a BCS vector: read a length and then apply function `cb` X times\n\t * where X is the length of the vector, defined as ULEB in BCS bytes.\n\t * @param cb Callback to process elements of vector.\n\t * @returns {Array<Any>} Array of the resulting values, returned by callback.\n\t */\n\treadVec(cb: (reader: BcsReader, i: number, length: number) => any): any[] {\n\t\tconst length = this.readULEB();\n\t\tconst result = [];\n\t\tfor (let i = 0; i < length; i++) {\n\t\t\tresult.push(cb(this, i, length));\n\t\t}\n\t\treturn result;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,YAAb,MAAuB;;;;CAOtB,YAAY,MAAkB;EALC,KAAA,eAAA;EAM9B,KAAK,WAAW,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;CAC3E;;;;;;;CAOA,MAAM,OAAe;EACpB,KAAK,gBAAgB;EACrB,OAAO;CACR;;;;;CAKA,QAAgB;EACf,MAAM,QAAQ,KAAK,SAAS,SAAS,KAAK,YAAY;EACtD,KAAK,MAAM,CAAC;EACZ,OAAO;CACR;;;;;CAKA,SAAiB;EAChB,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,cAAc,IAAI;EAC7D,KAAK,MAAM,CAAC;EACZ,OAAO;CACR;;;;;CAKA,SAAiB;EAChB,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,cAAc,IAAI;EAC7D,KAAK,MAAM,CAAC;EACZ,OAAO;CACR;;;;;CAKA,SAAiB;EAChB,MAAM,SAAS,KAAK,OAAO;EAG3B,MAAM,SAFS,KAAK,OAEA,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;EAExE,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,EAAE;CACzC;;;;CAIA,UAAkB;EACjB,MAAM,SAAS,OAAO,KAAK,OAAO,CAAC;EAEnC,MAAM,SADS,OAAO,KAAK,OAAO,CACd,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,GAAG;EAEzE,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,EAAE;CACzC;;;;;CAKA,UAAkB;EACjB,MAAM,SAAS,OAAO,KAAK,QAAQ,CAAC;EAEpC,MAAM,SADS,OAAO,KAAK,QAAQ,CACf,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,GAAG;EAEzE,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,EAAE;CACzC;;;;;CAKA,UAAU,KAAyB;EAClC,MAAM,QAAQ,KAAK,eAAe,KAAK,SAAS;EAChD,MAAM,QAAQ,IAAI,WAAW,KAAK,SAAS,QAAQ,OAAO,GAAG;EAE7D,KAAK,MAAM,GAAG;EAEd,OAAO;CACR;;;;;;CAMA,WAAmB;EAClB,MAAM,QAAQ,KAAK,eAAe,KAAK,SAAS;EAChD,MAAM,SAAS,IAAI,WAAW,KAAK,SAAS,QAAQ,KAAK;EACzD,MAAM,EAAE,OAAO,WAAW,WAAW,MAAM;EAE3C,KAAK,MAAM,MAAM;EAEjB,OAAO;CACR;;;;;;;CAOA,QAAQ,IAAkE;EACzE,MAAM,SAAS,KAAK,SAAS;EAC7B,MAAM,SAAS,CAAC;EAChB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAC3B,OAAO,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;EAEhC,OAAO;CACR;AACD"}
1
+ {"version":3,"file":"reader.mjs","names":[],"sources":["../src/reader.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Class used for reading BCS data chunk by chunk. Meant to be used\n * by some wrapper, which will make sure that data is valid and is\n * matching the desired format.\n *\n * @example\n * // data for this example is:\n * // { a: u8, b: u32, c: bool, d: u64 }\n *\n * let reader = new BcsReader(\"647f1a060001ffffe7890423c78a050102030405\");\n * let field1 = reader.read8();\n * let field2 = reader.read32();\n * let field3 = reader.read8() === '1'; // bool\n * let field4 = reader.read64();\n * // ....\n *\n * Reading vectors is another deal in bcs. To read a vector, you first need to read\n * its length using {@link readULEB}. Here's an example:\n * @example\n * // data encoded: { field: [1, 2, 3, 4, 5] }\n * let reader = new BcsReader(\"050102030405\");\n * let vec_length = reader.readULEB();\n * let elements = [];\n * for (let i = 0; i < vec_length; i++) {\n * elements.push(reader.read8());\n * }\n * console.log(elements); // [1,2,3,4,5]\n *\n * @param {String} data HEX-encoded data (serialized BCS)\n */\nexport class BcsReader {\n\tprivate dataView: DataView;\n\tprivate bytes: Uint8Array;\n\tprivate bytePosition: number = 0;\n\n\t/**\n\t * @param {Uint8Array} data Data to use as a buffer.\n\t */\n\tconstructor(data: Uint8Array) {\n\t\tthis.dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\t\tthis.bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n\t}\n\t/**\n\t * Shift current cursor position by `bytes`.\n\t *\n\t * @param {Number} bytes Number of bytes to\n\t * @returns {this} Self for possible chaining.\n\t */\n\tshift(bytes: number) {\n\t\tthis.bytePosition += bytes;\n\t\treturn this;\n\t}\n\t/**\n\t * Read U8 value from the buffer and shift cursor by 1.\n\t * @returns\n\t */\n\tread8(): number {\n\t\tconst position = this.bytePosition;\n\t\tif (position >= this.bytes.length) {\n\t\t\tthrow new RangeError('Offset is outside the bounds of the DataView');\n\t\t}\n\t\tthis.bytePosition = position + 1;\n\t\treturn this.bytes[position];\n\t}\n\t/**\n\t * Read U16 value from the buffer and shift cursor by 2.\n\t * @returns\n\t */\n\tread16(): number {\n\t\tconst value = this.dataView.getUint16(this.bytePosition, true);\n\t\tthis.shift(2);\n\t\treturn value;\n\t}\n\t/**\n\t * Read U32 value from the buffer and shift cursor by 4.\n\t * @returns\n\t */\n\tread32(): number {\n\t\tconst value = this.dataView.getUint32(this.bytePosition, true);\n\t\tthis.shift(4);\n\t\treturn value;\n\t}\n\t/**\n\t * Read U64 value from the buffer and shift cursor by 8.\n\t * @returns\n\t */\n\tread64(): string {\n\t\tconst value = this.dataView.getBigUint64(this.bytePosition, true);\n\t\tthis.shift(8);\n\t\treturn value.toString(10);\n\t}\n\t/**\n\t * Read U128 value from the buffer and shift cursor by 16.\n\t */\n\tread128(): string {\n\t\tconst value1 = BigInt(this.read64());\n\t\tconst value2 = BigInt(this.read64());\n\t\tconst result = value2.toString(16) + value1.toString(16).padStart(16, '0');\n\n\t\treturn BigInt('0x' + result).toString(10);\n\t}\n\t/**\n\t * Read U128 value from the buffer and shift cursor by 32.\n\t * @returns\n\t */\n\tread256(): string {\n\t\tconst value1 = BigInt(this.read128());\n\t\tconst value2 = BigInt(this.read128());\n\t\tconst result = value2.toString(16) + value1.toString(16).padStart(32, '0');\n\n\t\treturn BigInt('0x' + result).toString(10);\n\t}\n\t/**\n\t * Read `num` number of bytes from the buffer and shift cursor by `num`.\n\t * @param num Number of bytes to read.\n\t */\n\treadBytes(num: number): Uint8Array {\n\t\t// The underlying buffer may extend past this view, so check against the view's own length\n\t\tif (this.bytePosition + num > this.bytes.length) {\n\t\t\tthrow new RangeError('Offset is outside the bounds of the DataView');\n\t\t}\n\t\tconst start = this.bytePosition + this.dataView.byteOffset;\n\t\tconst value = new Uint8Array(this.dataView.buffer, start, num);\n\n\t\tthis.shift(num);\n\n\t\treturn value;\n\t}\n\t/**\n\t * Read ULEB value - an integer of varying size. Used for enum indexes and\n\t * vector lengths.\n\t * @returns {Number} The ULEB value.\n\t */\n\treadULEB(): number {\n\t\tconst bytes = this.bytes;\n\t\tlet position = this.bytePosition;\n\t\tlet value = 0;\n\t\tlet multiplier = 1;\n\t\twhile (true) {\n\t\t\tif (position >= bytes.length) {\n\t\t\t\tthrow new Error('ULEB decode error: buffer overflow');\n\t\t\t}\n\t\t\tconst byte = bytes[position++];\n\t\t\tvalue += (byte & 0x7f) * multiplier;\n\t\t\tif ((byte & 0x80) === 0) {\n\t\t\t\t// A trailing zero group adds no value, so the encoding is not minimal\n\t\t\t\tif (byte === 0 && position - this.bytePosition > 1) {\n\t\t\t\t\tthrow new Error('ULEB decode error: non-canonical encoding');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmultiplier *= 128;\n\t\t}\n\n\t\t// Also rejects NaN, from very long encodings where multiplier overflows to Infinity\n\t\tif (!(value <= Number.MAX_SAFE_INTEGER)) {\n\t\t\tthrow new Error('ULEB decode error: value exceeds MAX_SAFE_INTEGER');\n\t\t}\n\t\tthis.bytePosition = position;\n\t\treturn value;\n\t}\n\t/**\n\t * Read a BCS vector: read a length and then apply function `cb` X times\n\t * where X is the length of the vector, defined as ULEB in BCS bytes.\n\t * @param cb Callback to process elements of vector.\n\t * @returns {Array<Any>} Array of the resulting values, returned by callback.\n\t */\n\treadVec(cb: (reader: BcsReader, i: number, length: number) => any): any[] {\n\t\tconst length = this.readULEB();\n\t\tconst result = [];\n\t\tfor (let i = 0; i < length; i++) {\n\t\t\tresult.push(cb(this, i, length));\n\t\t}\n\t\treturn result;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,YAAb,MAAuB;;;;CAQtB,YAAY,MAAkB;EALC,KAAA,eAAA;EAM9B,KAAK,WAAW,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;EAC1E,KAAK,QAAQ,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;CAC1E;;;;;;;CAOA,MAAM,OAAe;EACpB,KAAK,gBAAgB;EACrB,OAAO;CACR;;;;;CAKA,QAAgB;EACf,MAAM,WAAW,KAAK;EACtB,IAAI,YAAY,KAAK,MAAM,QAC1B,MAAM,IAAI,WAAW,8CAA8C;EAEpE,KAAK,eAAe,WAAW;EAC/B,OAAO,KAAK,MAAM;CACnB;;;;;CAKA,SAAiB;EAChB,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,cAAc,IAAI;EAC7D,KAAK,MAAM,CAAC;EACZ,OAAO;CACR;;;;;CAKA,SAAiB;EAChB,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,cAAc,IAAI;EAC7D,KAAK,MAAM,CAAC;EACZ,OAAO;CACR;;;;;CAKA,SAAiB;EAChB,MAAM,QAAQ,KAAK,SAAS,aAAa,KAAK,cAAc,IAAI;EAChE,KAAK,MAAM,CAAC;EACZ,OAAO,MAAM,SAAS,EAAE;CACzB;;;;CAIA,UAAkB;EACjB,MAAM,SAAS,OAAO,KAAK,OAAO,CAAC;EAEnC,MAAM,SADS,OAAO,KAAK,OAAO,CACd,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,GAAG;EAEzE,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,EAAE;CACzC;;;;;CAKA,UAAkB;EACjB,MAAM,SAAS,OAAO,KAAK,QAAQ,CAAC;EAEpC,MAAM,SADS,OAAO,KAAK,QAAQ,CACf,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,GAAG;EAEzE,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,EAAE;CACzC;;;;;CAKA,UAAU,KAAyB;EAElC,IAAI,KAAK,eAAe,MAAM,KAAK,MAAM,QACxC,MAAM,IAAI,WAAW,8CAA8C;EAEpE,MAAM,QAAQ,KAAK,eAAe,KAAK,SAAS;EAChD,MAAM,QAAQ,IAAI,WAAW,KAAK,SAAS,QAAQ,OAAO,GAAG;EAE7D,KAAK,MAAM,GAAG;EAEd,OAAO;CACR;;;;;;CAMA,WAAmB;EAClB,MAAM,QAAQ,KAAK;EACnB,IAAI,WAAW,KAAK;EACpB,IAAI,QAAQ;EACZ,IAAI,aAAa;EACjB,OAAO,MAAM;GACZ,IAAI,YAAY,MAAM,QACrB,MAAM,IAAI,MAAM,oCAAoC;GAErD,MAAM,OAAO,MAAM;GACnB,UAAU,OAAO,OAAQ;GACzB,KAAK,OAAO,SAAU,GAAG;IAExB,IAAI,SAAS,KAAK,WAAW,KAAK,eAAe,GAChD,MAAM,IAAI,MAAM,2CAA2C;IAE5D;GACD;GACA,cAAc;EACf;EAGA,IAAI,EAAE,SAAS,OAAO,mBACrB,MAAM,IAAI,MAAM,mDAAmD;EAEpE,KAAK,eAAe;EACpB,OAAO;CACR;;;;;;;CAOA,QAAQ,IAAkE;EACzE,MAAM,SAAS,KAAK,SAAS;EAC7B,MAAM,SAAS,CAAC;EAChB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAC3B,OAAO,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;EAEhC,OAAO;CACR;AACD"}
package/dist/writer.d.mts CHANGED
@@ -27,6 +27,7 @@ interface BcsWriterOptions {
27
27
  */
28
28
  declare class BcsWriter {
29
29
  private dataView;
30
+ private bytes;
30
31
  private bytePosition;
31
32
  private size;
32
33
  private maxSize;
@@ -84,6 +85,7 @@ declare class BcsWriter {
84
85
  * @returns {this}
85
86
  */
86
87
  write256(value: number | bigint): this;
88
+ private writeBigLittleEndian;
87
89
  /**
88
90
  * Write a ULEB value into a buffer and shift cursor position by number of bytes
89
91
  * written.
@@ -1 +1 @@
1
- {"version":3,"file":"writer.d.mts","names":[],"sources":["../src/writer.ts"],"mappings":";;UAOiB;;EAEhB;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;cAqBY;UACJ;UACA;UACA;UACA;UACA;EAER,cACC,aACA,SACA,iBACE;UAOK;;;;;;;EA0BR,MAAM;;;;;;EASN,OAAO;;;;;;EAWP,WAAW,OAAO;;;;;;EAclB,QAAQ;;;;;;EAUR,QAAQ;;;;;;EAUR,QAAQ;;;;;;;EAWR,SAAS;;;;;;;EAWT,SAAS;;;;;;;EAWT,UAAU;;;;;;;;;EAYV,SAAS,eAAe,KAAK,QAAQ,WAAW,SAAS,WAAW;;;;;GAWlE,OAAO,aAAa,iBAAiB;;;;;EAWvC,WAAW,WAAW;;;;;EAQtB,SAAS,UAAU"}
1
+ {"version":3,"file":"writer.d.mts","names":[],"sources":["../src/writer.ts"],"mappings":";;UAOiB;;EAEhB;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;cAqBY;UACJ;UACA;UACA;UACA;UACA;UACA;EAER,cACC,aACA,SACA,iBACE;UAQK;;;;;;;EA4BR,MAAM;;;;;;EASN,OAAO;;;;;;EAWP,WAAW,OAAO;;;;;;EAWlB,QAAQ;;;;;;EAUR,QAAQ;;;;;;EAUR,QAAQ;;;;;;;EASR,SAAS;;;;;;;EAST,SAAS;UAKD;;;;;;;EAgBR,UAAU;;;;;;;;;EAqBV,SAAS,eAAe,KAAK,QAAQ,WAAW,SAAS,WAAW;;;;;GAWlE,OAAO,aAAa,iBAAiB;;;;;EAWvC,WAAW,WAAW;;;;;EAQtB,SAAS,UAAU"}
package/dist/writer.mjs CHANGED
@@ -25,6 +25,7 @@ var BcsWriter = class {
25
25
  this.maxSize = maxSize;
26
26
  this.allocateSize = allocateSize;
27
27
  this.dataView = new DataView(new ArrayBuffer(initialSize));
28
+ this.bytes = new Uint8Array(this.dataView.buffer);
28
29
  }
29
30
  ensureSizeOrGrow(bytes) {
30
31
  const requiredSize = this.bytePosition + bytes;
@@ -33,8 +34,10 @@ var BcsWriter = class {
33
34
  if (requiredSize > nextSize) throw new Error(`Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`);
34
35
  this.size = nextSize;
35
36
  const nextBuffer = new ArrayBuffer(this.size);
36
- new Uint8Array(nextBuffer).set(new Uint8Array(this.dataView.buffer));
37
+ const nextBytes = new Uint8Array(nextBuffer);
38
+ nextBytes.set(this.bytes);
37
39
  this.dataView = new DataView(nextBuffer);
40
+ this.bytes = nextBytes;
38
41
  }
39
42
  }
40
43
  /**
@@ -54,7 +57,7 @@ var BcsWriter = class {
54
57
  */
55
58
  write8(value) {
56
59
  this.ensureSizeOrGrow(1);
57
- this.dataView.setUint8(this.bytePosition, Number(value));
60
+ this.bytes[this.bytePosition] = Number(value);
58
61
  return this.shift(1);
59
62
  }
60
63
  /**
@@ -64,7 +67,7 @@ var BcsWriter = class {
64
67
  */
65
68
  writeBytes(bytes) {
66
69
  this.ensureSizeOrGrow(bytes.length);
67
- for (let i = 0; i < bytes.length; i++) this.dataView.setUint8(this.bytePosition + i, bytes[i]);
70
+ this.bytes.set(bytes, this.bytePosition);
68
71
  return this.shift(bytes.length);
69
72
  }
70
73
  /**
@@ -93,8 +96,7 @@ var BcsWriter = class {
93
96
  * @returns {this}
94
97
  */
95
98
  write64(value) {
96
- toLittleEndian(BigInt(value), 8).forEach((el) => this.write8(el));
97
- return this;
99
+ return this.writeBigLittleEndian(BigInt(value), 8);
98
100
  }
99
101
  /**
100
102
  * Write a U128 value into a buffer and shift cursor position by 16.
@@ -103,8 +105,7 @@ var BcsWriter = class {
103
105
  * @returns {this}
104
106
  */
105
107
  write128(value) {
106
- toLittleEndian(BigInt(value), 16).forEach((el) => this.write8(el));
107
- return this;
108
+ return this.writeBigLittleEndian(BigInt(value), 16);
108
109
  }
109
110
  /**
110
111
  * Write a U256 value into a buffer and shift cursor position by 16.
@@ -113,8 +114,16 @@ var BcsWriter = class {
113
114
  * @returns {this}
114
115
  */
115
116
  write256(value) {
116
- toLittleEndian(BigInt(value), 32).forEach((el) => this.write8(el));
117
- return this;
117
+ return this.writeBigLittleEndian(BigInt(value), 32);
118
+ }
119
+ writeBigLittleEndian(value, size) {
120
+ this.ensureSizeOrGrow(size);
121
+ let remaining = value > 0n ? value : 0n;
122
+ for (let offset = 0; offset < size; offset += 8) {
123
+ this.dataView.setBigUint64(this.bytePosition + offset, BigInt.asUintN(64, remaining), true);
124
+ remaining >>= 64n;
125
+ }
126
+ return this.shift(size);
118
127
  }
119
128
  /**
120
129
  * Write a ULEB value into a buffer and shift cursor position by number of bytes
@@ -123,6 +132,14 @@ var BcsWriter = class {
123
132
  * @returns {this}
124
133
  */
125
134
  writeULEB(value) {
135
+ if (value >= 0 && Number.isSafeInteger(value)) {
136
+ let remaining = value;
137
+ while (remaining >= 128) {
138
+ this.write8(remaining % 128 | 128);
139
+ remaining = Math.floor(remaining / 128);
140
+ }
141
+ return this.write8(remaining);
142
+ }
126
143
  ulebEncode(value).forEach((el) => this.write8(el));
127
144
  return this;
128
145
  }
@@ -162,16 +179,6 @@ var BcsWriter = class {
162
179
  return encodeStr(this.toBytes(), encoding);
163
180
  }
164
181
  };
165
- function toLittleEndian(bigint, size) {
166
- const result = new Uint8Array(size);
167
- let i = 0;
168
- while (bigint > 0) {
169
- result[i] = Number(bigint % BigInt(256));
170
- bigint = bigint / BigInt(256);
171
- i += 1;
172
- }
173
- return result;
174
- }
175
182
  //#endregion
176
183
  export { BcsWriter };
177
184
 
@@ -1 +1 @@
1
- {"version":3,"file":"writer.mjs","names":[],"sources":["../src/writer.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Encoding } from './types.js';\nimport { ulebEncode } from './uleb.js';\nimport { encodeStr } from './utils.js';\n\nexport interface BcsWriterOptions {\n\t/** The initial size (in bytes) of the buffer tht will be allocated */\n\tinitialSize?: number;\n\t/** The maximum size (in bytes) that the buffer is allowed to grow to */\n\tmaxSize?: number;\n\t/** The amount of bytes that will be allocated whenever additional memory is required */\n\tallocateSize?: number;\n}\n\n/**\n * Class used to write BCS data into a buffer. Initializer requires\n * some size of a buffer to init; default value for this buffer is 1KB.\n *\n * Most methods are chainable, so it is possible to write them in one go.\n *\n * @example\n * let serialized = new BcsWriter()\n * .write8(10)\n * .write32(1000000)\n * .write64(10000001000000)\n * .hex();\n */\n\n/**\n * Set of methods that allows data encoding/decoding as standalone\n * BCS value or a part of a composed structure/vector.\n */\nexport class BcsWriter {\n\tprivate dataView: DataView<ArrayBuffer>;\n\tprivate bytePosition: number = 0;\n\tprivate size: number;\n\tprivate maxSize: number;\n\tprivate allocateSize: number;\n\n\tconstructor({\n\t\tinitialSize = 1024,\n\t\tmaxSize = Infinity,\n\t\tallocateSize = 1024,\n\t}: BcsWriterOptions = {}) {\n\t\tthis.size = initialSize;\n\t\tthis.maxSize = maxSize;\n\t\tthis.allocateSize = allocateSize;\n\t\tthis.dataView = new DataView(new ArrayBuffer(initialSize));\n\t}\n\n\tprivate ensureSizeOrGrow(bytes: number) {\n\t\tconst requiredSize = this.bytePosition + bytes;\n\t\tif (requiredSize > this.size) {\n\t\t\tconst nextSize = Math.min(\n\t\t\t\tthis.maxSize,\n\t\t\t\tMath.max(this.size + requiredSize, this.size + this.allocateSize),\n\t\t\t);\n\t\t\tif (requiredSize > nextSize) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.size = nextSize;\n\t\t\tconst nextBuffer = new ArrayBuffer(this.size);\n\t\t\tnew Uint8Array(nextBuffer).set(new Uint8Array(this.dataView.buffer));\n\t\t\tthis.dataView = new DataView(nextBuffer);\n\t\t}\n\t}\n\n\t/**\n\t * Shift current cursor position by `bytes`.\n\t *\n\t * @param {Number} bytes Number of bytes to\n\t * @returns {this} Self for possible chaining.\n\t */\n\tshift(bytes: number): this {\n\t\tthis.bytePosition += bytes;\n\t\treturn this;\n\t}\n\t/**\n\t * Write a U8 value into a buffer and shift cursor position by 1.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite8(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(1);\n\t\tthis.dataView.setUint8(this.bytePosition, Number(value));\n\t\treturn this.shift(1);\n\t}\n\n\t/**\n\t * Write a U8 value into a buffer and shift cursor position by 1.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twriteBytes(bytes: Uint8Array): this {\n\t\tthis.ensureSizeOrGrow(bytes.length);\n\n\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\tthis.dataView.setUint8(this.bytePosition + i, bytes[i]);\n\t\t}\n\n\t\treturn this.shift(bytes.length);\n\t}\n\t/**\n\t * Write a U16 value into a buffer and shift cursor position by 2.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite16(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(2);\n\t\tthis.dataView.setUint16(this.bytePosition, Number(value), true);\n\t\treturn this.shift(2);\n\t}\n\t/**\n\t * Write a U32 value into a buffer and shift cursor position by 4.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite32(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(4);\n\t\tthis.dataView.setUint32(this.bytePosition, Number(value), true);\n\t\treturn this.shift(4);\n\t}\n\t/**\n\t * Write a U64 value into a buffer and shift cursor position by 8.\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite64(value: number | bigint): this {\n\t\ttoLittleEndian(BigInt(value), 8).forEach((el) => this.write8(el));\n\n\t\treturn this;\n\t}\n\t/**\n\t * Write a U128 value into a buffer and shift cursor position by 16.\n\t *\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite128(value: number | bigint): this {\n\t\ttoLittleEndian(BigInt(value), 16).forEach((el) => this.write8(el));\n\n\t\treturn this;\n\t}\n\t/**\n\t * Write a U256 value into a buffer and shift cursor position by 16.\n\t *\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite256(value: number | bigint): this {\n\t\ttoLittleEndian(BigInt(value), 32).forEach((el) => this.write8(el));\n\n\t\treturn this;\n\t}\n\t/**\n\t * Write a ULEB value into a buffer and shift cursor position by number of bytes\n\t * written.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twriteULEB(value: number): this {\n\t\tulebEncode(value).forEach((el) => this.write8(el));\n\t\treturn this;\n\t}\n\t/**\n\t * Write a vector into a buffer by first writing the vector length and then calling\n\t * a callback on each passed value.\n\t *\n\t * @param {Array<Any>} vector Array of elements to write.\n\t * @param {WriteVecCb} cb Callback to call on each element of the vector.\n\t * @returns {this}\n\t */\n\twriteVec(vector: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this {\n\t\tthis.writeULEB(vector.length);\n\t\tArray.from(vector).forEach((el, i) => cb(this, el, i, vector.length));\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds support for iterations over the object.\n\t * @returns {Uint8Array}\n\t */\n\t// oxlint-disable-next-line require-yields\n\t*[Symbol.iterator](): Iterator<number, Iterable<number>> {\n\t\tfor (let i = 0; i < this.bytePosition; i++) {\n\t\t\tyield this.dataView.getUint8(i);\n\t\t}\n\t\treturn this.toBytes();\n\t}\n\n\t/**\n\t * Get underlying buffer taking only value bytes (in case initial buffer size was bigger).\n\t * @returns {Uint8Array} Resulting bcs.\n\t */\n\ttoBytes(): Uint8Array<ArrayBuffer> {\n\t\treturn new Uint8Array(this.dataView.buffer.slice(0, this.bytePosition));\n\t}\n\n\t/**\n\t * Represent data as 'hex' or 'base64'\n\t * @param encoding Encoding to use: 'base64' or 'hex'\n\t */\n\ttoString(encoding: Encoding): string {\n\t\treturn encodeStr(this.toBytes(), encoding);\n\t}\n}\n\nfunction toLittleEndian(bigint: bigint, size: number) {\n\tconst result = new Uint8Array(size);\n\tlet i = 0;\n\twhile (bigint > 0) {\n\t\tresult[i] = Number(bigint % BigInt(256));\n\t\tbigint = bigint / BigInt(256);\n\t\ti += 1;\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"writer.mjs","names":[],"sources":["../src/writer.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Encoding } from './types.js';\nimport { ulebEncode } from './uleb.js';\nimport { encodeStr } from './utils.js';\n\nexport interface BcsWriterOptions {\n\t/** The initial size (in bytes) of the buffer tht will be allocated */\n\tinitialSize?: number;\n\t/** The maximum size (in bytes) that the buffer is allowed to grow to */\n\tmaxSize?: number;\n\t/** The amount of bytes that will be allocated whenever additional memory is required */\n\tallocateSize?: number;\n}\n\n/**\n * Class used to write BCS data into a buffer. Initializer requires\n * some size of a buffer to init; default value for this buffer is 1KB.\n *\n * Most methods are chainable, so it is possible to write them in one go.\n *\n * @example\n * let serialized = new BcsWriter()\n * .write8(10)\n * .write32(1000000)\n * .write64(10000001000000)\n * .hex();\n */\n\n/**\n * Set of methods that allows data encoding/decoding as standalone\n * BCS value or a part of a composed structure/vector.\n */\nexport class BcsWriter {\n\tprivate dataView: DataView<ArrayBuffer>;\n\tprivate bytes: Uint8Array<ArrayBuffer>;\n\tprivate bytePosition: number = 0;\n\tprivate size: number;\n\tprivate maxSize: number;\n\tprivate allocateSize: number;\n\n\tconstructor({\n\t\tinitialSize = 1024,\n\t\tmaxSize = Infinity,\n\t\tallocateSize = 1024,\n\t}: BcsWriterOptions = {}) {\n\t\tthis.size = initialSize;\n\t\tthis.maxSize = maxSize;\n\t\tthis.allocateSize = allocateSize;\n\t\tthis.dataView = new DataView(new ArrayBuffer(initialSize));\n\t\tthis.bytes = new Uint8Array(this.dataView.buffer);\n\t}\n\n\tprivate ensureSizeOrGrow(bytes: number) {\n\t\tconst requiredSize = this.bytePosition + bytes;\n\t\tif (requiredSize > this.size) {\n\t\t\tconst nextSize = Math.min(\n\t\t\t\tthis.maxSize,\n\t\t\t\tMath.max(this.size + requiredSize, this.size + this.allocateSize),\n\t\t\t);\n\t\t\tif (requiredSize > nextSize) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.size = nextSize;\n\t\t\tconst nextBuffer = new ArrayBuffer(this.size);\n\t\t\tconst nextBytes = new Uint8Array(nextBuffer);\n\t\t\tnextBytes.set(this.bytes);\n\t\t\tthis.dataView = new DataView(nextBuffer);\n\t\t\tthis.bytes = nextBytes;\n\t\t}\n\t}\n\n\t/**\n\t * Shift current cursor position by `bytes`.\n\t *\n\t * @param {Number} bytes Number of bytes to\n\t * @returns {this} Self for possible chaining.\n\t */\n\tshift(bytes: number): this {\n\t\tthis.bytePosition += bytes;\n\t\treturn this;\n\t}\n\t/**\n\t * Write a U8 value into a buffer and shift cursor position by 1.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite8(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(1);\n\t\tthis.bytes[this.bytePosition] = Number(value);\n\t\treturn this.shift(1);\n\t}\n\n\t/**\n\t * Write a U8 value into a buffer and shift cursor position by 1.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twriteBytes(bytes: Uint8Array): this {\n\t\tthis.ensureSizeOrGrow(bytes.length);\n\t\tthis.bytes.set(bytes, this.bytePosition);\n\n\t\treturn this.shift(bytes.length);\n\t}\n\t/**\n\t * Write a U16 value into a buffer and shift cursor position by 2.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite16(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(2);\n\t\tthis.dataView.setUint16(this.bytePosition, Number(value), true);\n\t\treturn this.shift(2);\n\t}\n\t/**\n\t * Write a U32 value into a buffer and shift cursor position by 4.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twrite32(value: number | bigint): this {\n\t\tthis.ensureSizeOrGrow(4);\n\t\tthis.dataView.setUint32(this.bytePosition, Number(value), true);\n\t\treturn this.shift(4);\n\t}\n\t/**\n\t * Write a U64 value into a buffer and shift cursor position by 8.\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite64(value: number | bigint): this {\n\t\treturn this.writeBigLittleEndian(BigInt(value), 8);\n\t}\n\t/**\n\t * Write a U128 value into a buffer and shift cursor position by 16.\n\t *\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite128(value: number | bigint): this {\n\t\treturn this.writeBigLittleEndian(BigInt(value), 16);\n\t}\n\t/**\n\t * Write a U256 value into a buffer and shift cursor position by 16.\n\t *\n\t * @param {bigint} value Value to write.\n\t * @returns {this}\n\t */\n\twrite256(value: number | bigint): this {\n\t\treturn this.writeBigLittleEndian(BigInt(value), 32);\n\t}\n\n\t// Negative values encode as zero and values that overflow `size` keep their low bytes\n\tprivate writeBigLittleEndian(value: bigint, size: number): this {\n\t\tthis.ensureSizeOrGrow(size);\n\t\tlet remaining = value > 0n ? value : 0n;\n\t\tfor (let offset = 0; offset < size; offset += 8) {\n\t\t\tthis.dataView.setBigUint64(this.bytePosition + offset, BigInt.asUintN(64, remaining), true);\n\t\t\tremaining >>= 64n;\n\t\t}\n\n\t\treturn this.shift(size);\n\t}\n\t/**\n\t * Write a ULEB value into a buffer and shift cursor position by number of bytes\n\t * written.\n\t * @param {Number} value Value to write.\n\t * @returns {this}\n\t */\n\twriteULEB(value: number): this {\n\t\tif (value >= 0 && Number.isSafeInteger(value)) {\n\t\t\tlet remaining = value;\n\t\t\twhile (remaining >= 0x80) {\n\t\t\t\tthis.write8((remaining % 0x80) | 0x80);\n\t\t\t\tremaining = Math.floor(remaining / 0x80);\n\t\t\t}\n\t\t\treturn this.write8(remaining);\n\t\t}\n\n\t\tulebEncode(value).forEach((el) => this.write8(el));\n\t\treturn this;\n\t}\n\t/**\n\t * Write a vector into a buffer by first writing the vector length and then calling\n\t * a callback on each passed value.\n\t *\n\t * @param {Array<Any>} vector Array of elements to write.\n\t * @param {WriteVecCb} cb Callback to call on each element of the vector.\n\t * @returns {this}\n\t */\n\twriteVec(vector: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this {\n\t\tthis.writeULEB(vector.length);\n\t\tArray.from(vector).forEach((el, i) => cb(this, el, i, vector.length));\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds support for iterations over the object.\n\t * @returns {Uint8Array}\n\t */\n\t// oxlint-disable-next-line require-yields\n\t*[Symbol.iterator](): Iterator<number, Iterable<number>> {\n\t\tfor (let i = 0; i < this.bytePosition; i++) {\n\t\t\tyield this.dataView.getUint8(i);\n\t\t}\n\t\treturn this.toBytes();\n\t}\n\n\t/**\n\t * Get underlying buffer taking only value bytes (in case initial buffer size was bigger).\n\t * @returns {Uint8Array} Resulting bcs.\n\t */\n\ttoBytes(): Uint8Array<ArrayBuffer> {\n\t\treturn new Uint8Array(this.dataView.buffer.slice(0, this.bytePosition));\n\t}\n\n\t/**\n\t * Represent data as 'hex' or 'base64'\n\t * @param encoding Encoding to use: 'base64' or 'hex'\n\t */\n\ttoString(encoding: Encoding): string {\n\t\treturn encodeStr(this.toBytes(), encoding);\n\t}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haneullabs/bcs",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "BCS - Canonical Binary Serialization implementation for JavaScript",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Haneul Labs <build@haneul-labs.com>",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@scure/base": "^2.3.0",
54
- "@haneullabs/utils": "^0.4.1"
54
+ "@haneullabs/utils": "^0.4.2"
55
55
  },
56
56
  "scripts": {
57
57
  "clean": "rm -rf tsconfig.tsbuildinfo ./dist",
package/src/bcs-type.ts CHANGED
@@ -22,6 +22,12 @@ export class BcsType<T, Input = T, const Name extends string = string> {
22
22
  validate: (value: Input) => void;
23
23
  #write: (value: Input, writer: BcsWriter) => void;
24
24
  #serialize: (value: Input, options?: BcsWriterOptions) => Uint8Array<ArrayBuffer>;
25
+ // Set by transform: validates and writes while converting the input only once. Only used
26
+ // while validate is still the one transform created, so a reassigned validate is honoured.
27
+ #validatedWrite: ((value: Input, writer: BcsWriter) => void) | null = null;
28
+ #validatedSerialize:
29
+ ((value: Input, options?: BcsWriterOptions) => Uint8Array<ArrayBuffer>) | null = null;
30
+ #transformValidate: ((value: Input) => void) | null = null;
25
31
 
26
32
  constructor(
27
33
  options: {
@@ -52,11 +58,18 @@ export class BcsType<T, Input = T, const Name extends string = string> {
52
58
  }
53
59
 
54
60
  write(value: Input, writer: BcsWriter) {
61
+ if (this.#validatedWrite && this.validate === this.#transformValidate) {
62
+ this.#validatedWrite(value, writer);
63
+ return;
64
+ }
55
65
  this.validate(value);
56
66
  this.#write(value, writer);
57
67
  }
58
68
 
59
69
  serialize(value: Input, options?: BcsWriterOptions) {
70
+ if (this.#validatedSerialize && this.validate === this.#transformValidate) {
71
+ return new SerializedBcs(this, this.#validatedSerialize(value, options));
72
+ }
60
73
  this.validate(value);
61
74
  return new SerializedBcs(this, this.#serialize(value, options));
62
75
  }
@@ -87,7 +100,7 @@ export class BcsType<T, Input = T, const Name extends string = string> {
87
100
  input?: (val: Input2) => Input;
88
101
  output?: (value: T) => T2;
89
102
  } & BcsTypeOptions<T2, Input2, NewName>) {
90
- return new BcsType<T2, Input2, NewName>({
103
+ const type = new BcsType<T2, Input2, NewName>({
91
104
  name: (name ?? this.name) as NewName,
92
105
  read: (reader) => (output ? output(this.read(reader)) : (this.read(reader) as never)),
93
106
  write: (value, writer) => this.#write(input ? input(value) : (value as never), writer),
@@ -99,6 +112,23 @@ export class BcsType<T, Input = T, const Name extends string = string> {
99
112
  this.validate(input ? input(value) : (value as never));
100
113
  },
101
114
  });
115
+
116
+ // Equivalent to validate() followed by the write above, but calls input() once instead of twice
117
+ type.#transformValidate = type.validate;
118
+ type.#validatedWrite = (value, writer) => {
119
+ validate?.(value);
120
+ const converted = input ? input(value) : (value as never);
121
+ this.validate(converted);
122
+ this.#write(converted, writer);
123
+ };
124
+ type.#validatedSerialize = (value, options) => {
125
+ validate?.(value);
126
+ const converted = input ? input(value) : (value as never);
127
+ this.validate(converted);
128
+ return this.#serialize(converted, options);
129
+ };
130
+
131
+ return type;
102
132
  }
103
133
  }
104
134
 
@@ -253,9 +283,7 @@ export function stringLikeBcsType<const Name extends string = string>({
253
283
  write: (hex, writer) => {
254
284
  const bytes = toBytes(hex);
255
285
  writer.writeULEB(bytes.length);
256
- for (let i = 0; i < bytes.length; i++) {
257
- writer.write8(bytes[i]);
258
- }
286
+ writer.writeBytes(bytes);
259
287
  },
260
288
  serialize: (value) => {
261
289
  const bytes = toBytes(value);
@@ -325,14 +353,16 @@ export class BcsStruct<
325
353
  Name
326
354
  > {
327
355
  constructor({ name, fields, ...options }: BcsStructOptions<T, Name>) {
328
- const canonicalOrder = Object.entries(fields);
356
+ const fieldNames = Object.keys(fields);
357
+ const fieldTypes = fieldNames.map((field) => fields[field]);
358
+ const fieldCount = fieldNames.length;
329
359
 
330
360
  super({
331
361
  name,
332
362
  serializedSize: (values) => {
333
363
  let total = 0;
334
- for (const [field, type] of canonicalOrder) {
335
- const size = type.serializedSize(values[field]);
364
+ for (let i = 0; i < fieldCount; i++) {
365
+ const size = fieldTypes[i].serializedSize(values[fieldNames[i]]);
336
366
  if (size == null) {
337
367
  return null;
338
368
  }
@@ -344,15 +374,15 @@ export class BcsStruct<
344
374
  },
345
375
  read: (reader) => {
346
376
  const result: Record<string, unknown> = {};
347
- for (const [field, type] of canonicalOrder) {
348
- result[field] = type.read(reader);
377
+ for (let i = 0; i < fieldCount; i++) {
378
+ result[fieldNames[i]] = fieldTypes[i].read(reader);
349
379
  }
350
380
 
351
381
  return result as never;
352
382
  },
353
383
  write: (value, writer) => {
354
- for (const [field, type] of canonicalOrder) {
355
- type.write(value[field], writer);
384
+ for (let i = 0; i < fieldCount; i++) {
385
+ fieldTypes[i].write(value[fieldNames[i]], writer);
356
386
  }
357
387
  },
358
388
  ...options,
@@ -399,6 +429,7 @@ export class BcsEnum<
399
429
  > {
400
430
  constructor({ fields, ...options }: BcsEnumOptions<T, Name>) {
401
431
  const canonicalOrder = Object.entries(fields as object);
432
+ const variantIndex = new Map(canonicalOrder.map(([name], i) => [name, i]));
402
433
  super({
403
434
  read: (reader) => {
404
435
  const index = reader.readULEB();
@@ -410,21 +441,26 @@ export class BcsEnum<
410
441
 
411
442
  const [kind, type] = enumEntry;
412
443
 
413
- return {
414
- [kind]: type?.read(reader) ?? true,
415
- $kind: kind,
416
- } as never;
444
+ const value = type?.read(reader) ?? true;
445
+ // Assigning to __proto__ would set the prototype rather than add a property
446
+ if (kind === '__proto__') {
447
+ return { [kind]: value, $kind: kind } as never;
448
+ }
449
+
450
+ // Avoid a computed-key object literal, which V8 builds through a slow runtime path
451
+ const result: Record<string, unknown> = {};
452
+ result[kind] = value;
453
+ result.$kind = kind;
454
+
455
+ return result as never;
417
456
  },
418
457
  write: (value, writer) => {
419
- const [name, val] = Object.entries(value).filter(([name]) =>
420
- Object.hasOwn(fields, name),
421
- )[0];
422
-
423
- for (let i = 0; i < canonicalOrder.length; i++) {
424
- const [optionName, optionType] = canonicalOrder[i];
425
- if (optionName === name) {
426
- writer.writeULEB(i);
427
- optionType?.write(val, writer);
458
+ // The first own key that names a variant selects it
459
+ for (const name of Object.keys(value)) {
460
+ const index = variantIndex.get(name);
461
+ if (index !== undefined) {
462
+ writer.writeULEB(index);
463
+ canonicalOrder[index][1]?.write(value[name], writer);
428
464
  return;
429
465
  }
430
466
  }
@@ -436,21 +472,18 @@ export class BcsEnum<
436
472
  throw new TypeError(`Expected object, found ${typeof value}`);
437
473
  }
438
474
 
439
- const keys = Object.keys(value).filter(
440
- (k) => value[k] !== undefined && Object.hasOwn(fields, k),
441
- );
475
+ let variantCount = 0;
476
+ for (const key of Object.keys(value)) {
477
+ if (value[key] !== undefined && variantIndex.has(key)) {
478
+ variantCount++;
479
+ }
480
+ }
442
481
 
443
- if (keys.length !== 1) {
482
+ if (variantCount !== 1) {
444
483
  throw new TypeError(
445
- `Expected object with one key, but found ${keys.length} for type ${options.name}}`,
484
+ `Expected object with one key, but found ${variantCount} for type ${options.name}}`,
446
485
  );
447
486
  }
448
-
449
- const [variant] = keys;
450
-
451
- if (!Object.hasOwn(fields, variant)) {
452
- throw new TypeError(`Invalid enum variant ${variant}`);
453
- }
454
487
  },
455
488
  });
456
489
  }
package/src/bcs.ts CHANGED
@@ -82,27 +82,34 @@ function option<T, Input, Name extends string = string>(
82
82
  function option<T extends BcsType<any>>(
83
83
  type: T,
84
84
  ): BcsType<InferBcsType<T> | null, InferBcsInput<T> | null | undefined, `Option<${T['name']}>`> {
85
- return bcs
86
- .enum(`Option<${type.name}>`, {
87
- None: null,
88
- Some: type,
89
- })
90
- .transform({
91
- input: (value: InferBcsInput<T> | null | undefined) => {
92
- if (value == null) {
93
- return { None: true };
94
- }
95
-
96
- return { Some: value };
97
- },
98
- output: (value) => {
99
- if (value.$kind === 'Some') {
100
- return value.Some as InferBcsType<T>;
101
- }
102
-
85
+ // Encoded like enum Option { None, Some(T) }, but read and written directly rather than
86
+ // through an intermediate { None } / { Some } enum object
87
+ const name = `Option<${type.name}>` as const;
88
+ return new BcsType<
89
+ InferBcsType<T> | null,
90
+ InferBcsInput<T> | null | undefined,
91
+ `Option<${T['name']}>`
92
+ >({
93
+ name,
94
+ read: (reader) => {
95
+ const index = reader.readULEB();
96
+ if (index === 0) {
103
97
  return null;
104
- },
105
- });
98
+ }
99
+ if (index === 1) {
100
+ return type.read(reader);
101
+ }
102
+ throw new TypeError(`Unknown value ${index} for enum ${name}`);
103
+ },
104
+ write: (value, writer) => {
105
+ if (value == null) {
106
+ writer.write8(0);
107
+ return;
108
+ }
109
+ writer.write8(1);
110
+ type.write(value, writer);
111
+ },
112
+ });
106
113
  }
107
114
 
108
115
  function vector<T extends BcsType<any>, Name extends string = `vector<${T['name']}>`>(
@@ -211,6 +218,32 @@ function map<K extends BcsType<any>, V extends BcsType<any>>(
211
218
  });
212
219
  }
213
220
 
221
+ // Created on first use, so importing the package doesn't require TextEncoder/TextDecoder
222
+ let utf8Encoder: TextEncoder | undefined;
223
+ let utf8Decoder: TextDecoder | undefined;
224
+ function decoder() {
225
+ return (utf8Decoder ??= new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }));
226
+ }
227
+
228
+ // Most strings in Haneul data (module, function and type names) are short ASCII. Decoding those
229
+ // directly avoids the fixed cost of a TextDecoder call; anything else goes through the decoder.
230
+ const ASCII_FAST_PATH_MAX_LENGTH = 64;
231
+ function decodeUtf8(bytes: Uint8Array): string {
232
+ if (bytes.length <= ASCII_FAST_PATH_MAX_LENGTH) {
233
+ let result = '';
234
+ for (let i = 0; i < bytes.length; i++) {
235
+ const byte = bytes[i];
236
+ if (byte >= 0x80) {
237
+ return decoder().decode(bytes);
238
+ }
239
+ result += String.fromCharCode(byte);
240
+ }
241
+ return result;
242
+ }
243
+
244
+ return decoder().decode(bytes);
245
+ }
246
+
214
247
  export const bcs = {
215
248
  /**
216
249
  * Creates a BcsType that can be used to read and write an 8-bit unsigned integer.
@@ -362,7 +395,7 @@ export const bcs = {
362
395
  size,
363
396
  read: (reader) => reader.readBytes(size),
364
397
  write: (value, writer) => {
365
- writer.writeBytes(new Uint8Array(value));
398
+ writer.writeBytes(value instanceof Uint8Array ? value : new Uint8Array(value));
366
399
  },
367
400
  ...options,
368
401
  name: (options?.name ?? `bytes[${size}]`) as `bytes[${T}]`,
@@ -392,7 +425,7 @@ export const bcs = {
392
425
  return reader.readBytes(length);
393
426
  },
394
427
  write: (value, writer) => {
395
- const array = new Uint8Array(value);
428
+ const array = value instanceof Uint8Array ? value : new Uint8Array(value);
396
429
  writer.writeULEB(array.length);
397
430
  writer.writeBytes(array);
398
431
  },
@@ -418,9 +451,8 @@ export const bcs = {
418
451
  */
419
452
  string(options?: BcsTypeOptions<string>) {
420
453
  return stringLikeBcsType({
421
- toBytes: (value) => new TextEncoder().encode(value),
422
- fromBytes: (bytes) =>
423
- new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes),
454
+ toBytes: (value) => (utf8Encoder ??= new TextEncoder()).encode(value),
455
+ fromBytes: decodeUtf8,
424
456
  ...options,
425
457
  name: (options?.name ?? 'string') as 'string',
426
458
  });
package/src/reader.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  // Copyright (c) Mysten Labs, Inc.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { ulebDecode } from './uleb.js';
5
-
6
4
  /**
7
5
  * Class used for reading BCS data chunk by chunk. Meant to be used
8
6
  * by some wrapper, which will make sure that data is valid and is
@@ -35,6 +33,7 @@ import { ulebDecode } from './uleb.js';
35
33
  */
36
34
  export class BcsReader {
37
35
  private dataView: DataView;
36
+ private bytes: Uint8Array;
38
37
  private bytePosition: number = 0;
39
38
 
40
39
  /**
@@ -42,6 +41,7 @@ export class BcsReader {
42
41
  */
43
42
  constructor(data: Uint8Array) {
44
43
  this.dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
44
+ this.bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
45
45
  }
46
46
  /**
47
47
  * Shift current cursor position by `bytes`.
@@ -58,9 +58,12 @@ export class BcsReader {
58
58
  * @returns
59
59
  */
60
60
  read8(): number {
61
- const value = this.dataView.getUint8(this.bytePosition);
62
- this.shift(1);
63
- return value;
61
+ const position = this.bytePosition;
62
+ if (position >= this.bytes.length) {
63
+ throw new RangeError('Offset is outside the bounds of the DataView');
64
+ }
65
+ this.bytePosition = position + 1;
66
+ return this.bytes[position];
64
67
  }
65
68
  /**
66
69
  * Read U16 value from the buffer and shift cursor by 2.
@@ -85,12 +88,9 @@ export class BcsReader {
85
88
  * @returns
86
89
  */
87
90
  read64(): string {
88
- const value1 = this.read32();
89
- const value2 = this.read32();
90
-
91
- const result = value2.toString(16) + value1.toString(16).padStart(8, '0');
92
-
93
- return BigInt('0x' + result).toString(10);
91
+ const value = this.dataView.getBigUint64(this.bytePosition, true);
92
+ this.shift(8);
93
+ return value.toString(10);
94
94
  }
95
95
  /**
96
96
  * Read U128 value from the buffer and shift cursor by 16.
@@ -118,6 +118,10 @@ export class BcsReader {
118
118
  * @param num Number of bytes to read.
119
119
  */
120
120
  readBytes(num: number): Uint8Array {
121
+ // The underlying buffer may extend past this view, so check against the view's own length
122
+ if (this.bytePosition + num > this.bytes.length) {
123
+ throw new RangeError('Offset is outside the bounds of the DataView');
124
+ }
121
125
  const start = this.bytePosition + this.dataView.byteOffset;
122
126
  const value = new Uint8Array(this.dataView.buffer, start, num);
123
127
 
@@ -131,12 +135,31 @@ export class BcsReader {
131
135
  * @returns {Number} The ULEB value.
132
136
  */
133
137
  readULEB(): number {
134
- const start = this.bytePosition + this.dataView.byteOffset;
135
- const buffer = new Uint8Array(this.dataView.buffer, start);
136
- const { value, length } = ulebDecode(buffer);
137
-
138
- this.shift(length);
138
+ const bytes = this.bytes;
139
+ let position = this.bytePosition;
140
+ let value = 0;
141
+ let multiplier = 1;
142
+ while (true) {
143
+ if (position >= bytes.length) {
144
+ throw new Error('ULEB decode error: buffer overflow');
145
+ }
146
+ const byte = bytes[position++];
147
+ value += (byte & 0x7f) * multiplier;
148
+ if ((byte & 0x80) === 0) {
149
+ // A trailing zero group adds no value, so the encoding is not minimal
150
+ if (byte === 0 && position - this.bytePosition > 1) {
151
+ throw new Error('ULEB decode error: non-canonical encoding');
152
+ }
153
+ break;
154
+ }
155
+ multiplier *= 128;
156
+ }
139
157
 
158
+ // Also rejects NaN, from very long encodings where multiplier overflows to Infinity
159
+ if (!(value <= Number.MAX_SAFE_INTEGER)) {
160
+ throw new Error('ULEB decode error: value exceeds MAX_SAFE_INTEGER');
161
+ }
162
+ this.bytePosition = position;
140
163
  return value;
141
164
  }
142
165
  /**