UNPKG

@ckb-lumos/codec

Version:

Make your own molecule binding in JavaScript(TypeScript)

340 lines (324 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.array = array; exports.dynvec = dynvec; exports.fixvec = fixvec; exports.option = option; exports.struct = struct; exports.table = table; exports.union = union; exports.vector = vector; var _base = require("../base"); var _number = require("../number"); var _bytes = require("../bytes"); var _error = require("../error"); var _highOrder = require("../high-order"); /** * | Type | Header | Body | * |--------+--------------------------------------------------+-----------------------------------| * | array | | item-0 | item-1 | ... | item-N | * | struct | | field-0 | field-1 | ... | field-N | * | fixvec | items-count | item-0 | item-1 | ... | item-N | * | dynvec | full-size | offset-0 | offset-1 | ... | offset-N | item-0 | item-1 | ... | item-N | * | table | full-size | offset-0 | offset-1 | ... | offset-N | filed-0 | field-1 | ... | field-N | * | option | | item or none (zero bytes) | * | union | item-type-id | item | */ // prettier-ignore /** * A codec for struct and table of Molecule */ /** * A codec for option of Molecule */ /** * A code for array and vector of Molecule */ /** * A molecule codec for ` */ /** * The array is a fixed-size type: it has a fixed-size inner type and a fixed length. * The size of an array is the size of inner type times the length. * @param itemCodec the fixed-size array item codec * @param itemCount */ function array(itemCodec, itemCount) { const enhancedArrayCodec = (0, _highOrder.createArrayCodec)(itemCodec); return (0, _base.createFixedBytesCodec)({ byteLength: itemCodec.byteLength * itemCount, pack(items) { const itemsBuf = enhancedArrayCodec.pack(items); return (0, _bytes.concat)(...itemsBuf); }, unpack(buf) { const result = []; const itemLength = itemCodec.byteLength; for (let offset = 0; offset < buf.byteLength; offset += itemLength) { result.push(itemCodec.unpack(buf.slice(offset, offset + itemLength))); } return result; } }); } function diff(x1, x2) { return x1.filter(x => !x2.includes(x)); } function checkShape(shape, fields) { const shapeKeys = Object.keys(shape); const missingFields = diff(shapeKeys, fields); const missingShape = diff(fields, shapeKeys); if (missingFields.length > 0 || missingShape.length > 0) { throw new Error(`Invalid shape: missing fields ${missingFields.join(", ")} or shape ${missingShape.join(", ")}`); } } /** * Struct is a fixed-size type: all fields in struct are fixed-size and it has a fixed quantity of fields. * The size of a struct is the sum of all fields' size. * @param shape a object contains all fields' codec * @param fields the shape's keys. It provide an order for serialization/deserialization. */ function struct(shape, fields) { checkShape(shape, fields); const objectCodec = (0, _highOrder.createObjectCodec)(shape); return (0, _base.createFixedBytesCodec)({ byteLength: fields.reduce((sum, field) => sum + shape[field].byteLength, 0), pack(obj) { const packed = objectCodec.pack(obj); return fields.reduce((result, field) => { return (0, _bytes.concat)(result, packed[field]); }, Uint8Array.from([])); }, unpack(buf) { const result = {}; let offset = 0; fields.forEach(field => { const itemCodec = shape[field]; const itemBuf = buf.slice(offset, offset + itemCodec.byteLength); Object.assign(result, { [field]: itemCodec.unpack(itemBuf) }); offset = offset + itemCodec.byteLength; }); return result; } }); } /** * Vector with fixed size item codec * @param itemCodec fixed-size vector item codec */ function fixvec(itemCodec) { return (0, _base.createBytesCodec)({ pack(items) { const arrayCodec = (0, _highOrder.createArrayCodec)(itemCodec); return (0, _bytes.concat)(_number.Uint32LE.pack(items.length), arrayCodec.pack(items).reduce((buf, item) => (0, _bytes.concat)(buf, item), new ArrayBuffer(0))); }, unpack(buf) { if (buf.byteLength < 4) { throw new Error(`fixvec: buffer is too short, expected at least 4 bytes, got ${buf.byteLength}`); } const itemCount = _number.Uint32LE.unpack(buf.slice(0, 4)); return array(itemCodec, itemCount).unpack(buf.slice(4)); } }); } /** * Vector with dynamic size item codec * @param itemCodec the vector item codec. It can be fixed-size or dynamic-size. * For example, you can create a recursive vector with this. */ function dynvec(itemCodec) { return (0, _base.createBytesCodec)({ pack(obj) { const arrayCodec = (0, _highOrder.createArrayCodec)(itemCodec); const packed = arrayCodec.pack(obj).reduce((result, item) => { const packedHeader = _number.Uint32LE.pack(result.offset); return { header: (0, _bytes.concat)(result.header, packedHeader), body: (0, _bytes.concat)(result.body, item), offset: result.offset + item.byteLength }; }, { header: new ArrayBuffer(0), body: new ArrayBuffer(0), offset: 4 + obj.length * 4 }); const packedTotalSize = _number.Uint32LE.pack(packed.header.byteLength + packed.body.byteLength + 4); return (0, _bytes.concat)(packedTotalSize, packed.header, packed.body); }, unpack(buf) { const totalSize = _number.Uint32LE.unpack(buf.slice(0, 4)); if (totalSize !== buf.byteLength) { throw new Error(`Invalid buffer size, read from header: ${totalSize}, actual: ${buf.byteLength}`); } const result = []; if (totalSize <= 4) { return result; } else { const offset0 = _number.Uint32LE.unpack(buf.slice(4, 8)); const itemCount = (offset0 - 4) / 4; const offsets = new Array(itemCount).fill(1).map((_, index) => _number.Uint32LE.unpack(buf.slice(4 + index * 4, 8 + index * 4))); offsets.push(totalSize); const result = []; for (let index = 0; index < offsets.length - 1; index++) { const start = offsets[index]; const end = offsets[index + 1]; const itemBuf = buf.slice(start, end); result.push(itemCodec.unpack(itemBuf)); } return result; } } }); } /** * General vector codec, if `itemCodec` is fixed size type, it will create a fixvec codec, otherwise a dynvec codec will be created. * @param itemCodec */ function vector(itemCodec) { if ((0, _base.isFixedCodec)(itemCodec)) { return fixvec(itemCodec); } return dynvec(itemCodec); } /** * Table is a dynamic-size type. It can be considered as a dynvec but the length is fixed. * @param shape The table shape, item codec can be dynamic size * @param fields the shape's keys. Also provide an order for pack/unpack. */ function table(shape, fields) { checkShape(shape, fields); return (0, _base.createBytesCodec)({ pack(obj) { const headerLength = 4 + fields.length * 4; const objectCodec = (0, _highOrder.createObjectCodec)(shape); const packedObj = objectCodec.pack(obj); const packed = fields.reduce((result, field) => { const packedItem = packedObj[field]; const packedOffset = _number.Uint32LE.pack(result.offset); return { header: (0, _bytes.concat)(result.header, packedOffset), body: (0, _bytes.concat)(result.body, packedItem), offset: result.offset + packedItem.byteLength }; }, { header: new ArrayBuffer(0), body: new ArrayBuffer(0), offset: headerLength }); const packedTotalSize = _number.Uint32LE.pack(packed.header.byteLength + packed.body.byteLength + 4); return (0, _bytes.concat)(packedTotalSize, packed.header, packed.body); }, unpack(buf) { const totalSize = _number.Uint32LE.unpack(buf.slice(0, 4)); if (totalSize !== buf.byteLength) { throw new Error(`Invalid buffer size, read from header: ${totalSize}, actual: ${buf.byteLength}`); } if (totalSize <= 4 || fields.length === 0) { return {}; } else { const offsets = fields.map((_, index) => _number.Uint32LE.unpack(buf.slice(4 + index * 4, 8 + index * 4))); offsets.push(totalSize); const obj = {}; for (let index = 0; index < offsets.length - 1; index++) { const start = offsets[index]; const end = offsets[index + 1]; const field = fields[index]; const itemCodec = shape[field]; const itemBuf = buf.slice(start, end); Object.assign(obj, { [field]: itemCodec.unpack(itemBuf) }); } return obj; } } }); } /** * Union is a dynamic-size type. * Serializing a union has two steps: * - Serialize an item type id in bytes as a 32 bit unsigned integer in little-endian. The item type id is the index of the inner items, and it's starting at 0. * - Serialize the inner item. * @param itemCodec the union item record * @param fields the union item keys, can be an array or an object with custom id * @example * // without custom id * union({ cafe: Uint8, bee: Uint8 }, ['cafe', 'bee']) * // with custom id * union({ cafe: Uint8, bee: Uint8 }, { cafe: 0xcafe, bee: 0xbee }) */ function union(itemCodec, fields) { checkShape(itemCodec, Array.isArray(fields) ? fields : Object.keys(fields)); // check duplicated id if (!Array.isArray(fields)) { const ids = Object.values(fields); if (ids.length !== new Set(ids).size) { throw new Error(`Duplicated id in union: ${ids.join(", ")}`); } } return (0, _base.createBytesCodec)({ pack(obj) { const availableFields = Object.keys(itemCodec); const type = obj.type; const typeName = `Union(${availableFields.join(" | ")})`; /* c8 ignore next */ if (typeof type !== "string") { throw new _error.CodecBaseParseError(`Invalid type in union, type must be a string`, typeName); } const fieldId = Array.isArray(fields) ? fields.indexOf(type) : fields[type]; if (fieldId < 0) { throw new _error.CodecBaseParseError(`Unknown union type: ${String(obj.type)}`, typeName); } const packedFieldIndex = _number.Uint32LE.pack(fieldId); const packedBody = itemCodec[type].pack(obj.value); return (0, _bytes.concat)(packedFieldIndex, packedBody); }, unpack(buf) { const fieldId = _number.Uint32LE.unpack(buf.slice(0, 4)); const type = (() => { if (Array.isArray(fields)) { return fields[fieldId]; } const entry = Object.entries(fields).find(([, id]) => id === fieldId); return entry === null || entry === void 0 ? void 0 : entry[0]; })(); if (!type) { throw new Error(`Unknown union field id: ${fieldId}, only ${fields} are allowed`); } return { type, value: itemCodec[type].unpack(buf.slice(4)) }; } }); } /** * Option is a dynamic-size type. * Serializing an option depends on whether it is empty or not: * - if it's empty, there is zero bytes (the size is 0). * - if it's not empty, just serialize the inner item (the size is same as the inner item's size). * @param itemCodec */ function option(itemCodec) { return (0, _base.createBytesCodec)({ pack(obj) { const nullableCodec = (0, _highOrder.createNullableCodec)(itemCodec); if (obj !== undefined && obj !== null) { return nullableCodec.pack(obj); } else { return Uint8Array.from([]); } }, unpack(buf) { if (buf.byteLength === 0) { return undefined; } return itemCodec.unpack(buf); } }); } //# sourceMappingURL=layout.js.map