@msgpack/msgpack
Version:
MessagePack for ECMA-262/JavaScript/TypeScript
486 lines • 15.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Encoder = exports.DEFAULT_INITIAL_BUFFER_SIZE = exports.DEFAULT_MAX_DEPTH = void 0;
const utf8_ts_1 = require("./utils/utf8.cjs");;
const ExtensionCodec_ts_1 = require("./ExtensionCodec.cjs");;
const int_ts_1 = require("./utils/int.cjs");;
const typedArrays_ts_1 = require("./utils/typedArrays.cjs");;
exports.DEFAULT_MAX_DEPTH = 100;
exports.DEFAULT_INITIAL_BUFFER_SIZE = 2048;
class Encoder {
constructor(options) {
this.entered = false;
this.extensionCodec = options?.extensionCodec ?? ExtensionCodec_ts_1.ExtensionCodec.defaultCodec;
this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
this.useBigInt64 = options?.useBigInt64 ?? false;
this.maxDepth = options?.maxDepth ?? exports.DEFAULT_MAX_DEPTH;
this.initialBufferSize = options?.initialBufferSize ?? exports.DEFAULT_INITIAL_BUFFER_SIZE;
this.sortKeys = options?.sortKeys ?? false;
this.forceFloat32 = options?.forceFloat32 ?? false;
this.ignoreUndefined = options?.ignoreUndefined ?? false;
this.forceIntegerToFloat = options?.forceIntegerToFloat ?? false;
this.pos = 0;
this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
this.bytes = new Uint8Array(this.view.buffer);
}
clone() {
// Because of slightly special argument `context`,
// type assertion is needed.
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return new Encoder({
extensionCodec: this.extensionCodec,
context: this.context,
useBigInt64: this.useBigInt64,
maxDepth: this.maxDepth,
initialBufferSize: this.initialBufferSize,
sortKeys: this.sortKeys,
forceFloat32: this.forceFloat32,
ignoreUndefined: this.ignoreUndefined,
forceIntegerToFloat: this.forceIntegerToFloat,
});
}
reinitializeState() {
this.pos = 0;
}
/**
* This is almost equivalent to {@link Encoder#encode}, but it returns an reference of the encoder's internal buffer and thus much faster than {@link Encoder#encode}.
*
* @returns Encodes the object and returns a shared reference the encoder's internal buffer.
*/
encodeSharedRef(object) {
if (this.entered) {
const instance = this.clone();
return instance.encodeSharedRef(object);
}
try {
this.entered = true;
this.reinitializeState();
this.doEncode(object, 1);
return this.bytes.subarray(0, this.pos);
}
finally {
this.entered = false;
}
}
/**
* @returns Encodes the object and returns a copy of the encoder's internal buffer.
*/
encode(object) {
if (this.entered) {
const instance = this.clone();
return instance.encode(object);
}
try {
this.entered = true;
this.reinitializeState();
this.doEncode(object, 1);
return this.bytes.slice(0, this.pos);
}
finally {
this.entered = false;
}
}
doEncode(object, depth) {
if (depth > this.maxDepth) {
throw new Error(`Too deep objects in depth ${depth}`);
}
if (object == null) {
this.encodeNil();
}
else if (typeof object === "boolean") {
this.encodeBoolean(object);
}
else if (typeof object === "number") {
if (!this.forceIntegerToFloat) {
this.encodeNumber(object);
}
else {
this.encodeNumberAsFloat(object);
}
}
else if (typeof object === "string") {
this.encodeString(object);
}
else if (this.useBigInt64 && typeof object === "bigint") {
this.encodeBigInt64(object);
}
else {
this.encodeObject(object, depth);
}
}
ensureBufferSizeToWrite(sizeToWrite) {
const requiredSize = this.pos + sizeToWrite;
if (this.view.byteLength < requiredSize) {
this.resizeBuffer(requiredSize * 2);
}
}
resizeBuffer(newSize) {
const newBuffer = new ArrayBuffer(newSize);
const newBytes = new Uint8Array(newBuffer);
const newView = new DataView(newBuffer);
newBytes.set(this.bytes);
this.view = newView;
this.bytes = newBytes;
}
encodeNil() {
this.writeU8(0xc0);
}
encodeBoolean(object) {
if (object === false) {
this.writeU8(0xc2);
}
else {
this.writeU8(0xc3);
}
}
encodeNumber(object) {
if (!this.forceIntegerToFloat && Number.isSafeInteger(object)) {
if (object >= 0) {
if (object < 0x80) {
// positive fixint
this.writeU8(object);
}
else if (object < 0x100) {
// uint 8
this.writeU8(0xcc);
this.writeU8(object);
}
else if (object < 0x10000) {
// uint 16
this.writeU8(0xcd);
this.writeU16(object);
}
else if (object < 0x100000000) {
// uint 32
this.writeU8(0xce);
this.writeU32(object);
}
else if (!this.useBigInt64) {
// uint 64
this.writeU8(0xcf);
this.writeU64(object);
}
else {
this.encodeNumberAsFloat(object);
}
}
else {
if (object >= -0x20) {
// negative fixint
this.writeU8(0xe0 | (object + 0x20));
}
else if (object >= -0x80) {
// int 8
this.writeU8(0xd0);
this.writeI8(object);
}
else if (object >= -0x8000) {
// int 16
this.writeU8(0xd1);
this.writeI16(object);
}
else if (object >= -0x80000000) {
// int 32
this.writeU8(0xd2);
this.writeI32(object);
}
else if (!this.useBigInt64) {
// int 64
this.writeU8(0xd3);
this.writeI64(object);
}
else {
this.encodeNumberAsFloat(object);
}
}
}
else {
this.encodeNumberAsFloat(object);
}
}
encodeNumberAsFloat(object) {
if (this.forceFloat32) {
// float 32
this.writeU8(0xca);
this.writeF32(object);
}
else {
// float 64
this.writeU8(0xcb);
this.writeF64(object);
}
}
encodeBigInt64(object) {
if (object >= BigInt(0)) {
// uint 64
this.writeU8(0xcf);
this.writeBigUint64(object);
}
else {
// int 64
this.writeU8(0xd3);
this.writeBigInt64(object);
}
}
writeStringHeader(byteLength) {
if (byteLength < 32) {
// fixstr
this.writeU8(0xa0 + byteLength);
}
else if (byteLength < 0x100) {
// str 8
this.writeU8(0xd9);
this.writeU8(byteLength);
}
else if (byteLength < 0x10000) {
// str 16
this.writeU8(0xda);
this.writeU16(byteLength);
}
else if (byteLength < 0x100000000) {
// str 32
this.writeU8(0xdb);
this.writeU32(byteLength);
}
else {
throw new Error(`Too long string: ${byteLength} bytes in UTF-8`);
}
}
encodeString(object) {
const maxHeaderSize = 1 + 4;
const byteLength = (0, utf8_ts_1.utf8Count)(object);
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
this.writeStringHeader(byteLength);
(0, utf8_ts_1.utf8Encode)(object, this.bytes, this.pos);
this.pos += byteLength;
}
encodeObject(object, depth) {
// try to encode objects with custom codec first of non-primitives
const ext = this.extensionCodec.tryToEncode(object, this.context);
if (ext != null) {
this.encodeExtension(ext);
}
else if (Array.isArray(object)) {
this.encodeArray(object, depth);
}
else if (ArrayBuffer.isView(object)) {
this.encodeBinary(object);
}
else if (typeof object === "object") {
this.encodeMap(object, depth);
}
else {
// symbol, function and other special object come here unless extensionCodec handles them.
throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`);
}
}
encodeBinary(object) {
const size = object.byteLength;
if (size < 0x100) {
// bin 8
this.writeU8(0xc4);
this.writeU8(size);
}
else if (size < 0x10000) {
// bin 16
this.writeU8(0xc5);
this.writeU16(size);
}
else if (size < 0x100000000) {
// bin 32
this.writeU8(0xc6);
this.writeU32(size);
}
else {
throw new Error(`Too large binary: ${size}`);
}
const bytes = (0, typedArrays_ts_1.ensureUint8Array)(object);
this.writeU8a(bytes);
}
encodeArray(object, depth) {
const size = object.length;
if (size < 16) {
// fixarray
this.writeU8(0x90 + size);
}
else if (size < 0x10000) {
// array 16
this.writeU8(0xdc);
this.writeU16(size);
}
else if (size < 0x100000000) {
// array 32
this.writeU8(0xdd);
this.writeU32(size);
}
else {
throw new Error(`Too large array: ${size}`);
}
for (const item of object) {
this.doEncode(item, depth + 1);
}
}
countWithoutUndefined(object, keys) {
let count = 0;
for (const key of keys) {
if (object[key] !== undefined) {
count++;
}
}
return count;
}
encodeMap(object, depth) {
const keys = Object.keys(object);
if (this.sortKeys) {
keys.sort();
}
const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length;
if (size < 16) {
// fixmap
this.writeU8(0x80 + size);
}
else if (size < 0x10000) {
// map 16
this.writeU8(0xde);
this.writeU16(size);
}
else if (size < 0x100000000) {
// map 32
this.writeU8(0xdf);
this.writeU32(size);
}
else {
throw new Error(`Too large map object: ${size}`);
}
for (const key of keys) {
const value = object[key];
if (!(this.ignoreUndefined && value === undefined)) {
this.encodeString(key);
this.doEncode(value, depth + 1);
}
}
}
encodeExtension(ext) {
if (typeof ext.data === "function") {
const data = ext.data(this.pos + 6);
const size = data.length;
if (size >= 0x100000000) {
throw new Error(`Too large extension object: ${size}`);
}
this.writeU8(0xc9);
this.writeU32(size);
this.writeI8(ext.type);
this.writeU8a(data);
return;
}
const size = ext.data.length;
if (size === 1) {
// fixext 1
this.writeU8(0xd4);
}
else if (size === 2) {
// fixext 2
this.writeU8(0xd5);
}
else if (size === 4) {
// fixext 4
this.writeU8(0xd6);
}
else if (size === 8) {
// fixext 8
this.writeU8(0xd7);
}
else if (size === 16) {
// fixext 16
this.writeU8(0xd8);
}
else if (size < 0x100) {
// ext 8
this.writeU8(0xc7);
this.writeU8(size);
}
else if (size < 0x10000) {
// ext 16
this.writeU8(0xc8);
this.writeU16(size);
}
else if (size < 0x100000000) {
// ext 32
this.writeU8(0xc9);
this.writeU32(size);
}
else {
throw new Error(`Too large extension object: ${size}`);
}
this.writeI8(ext.type);
this.writeU8a(ext.data);
}
writeU8(value) {
this.ensureBufferSizeToWrite(1);
this.view.setUint8(this.pos, value);
this.pos++;
}
writeU8a(values) {
const size = values.length;
this.ensureBufferSizeToWrite(size);
this.bytes.set(values, this.pos);
this.pos += size;
}
writeI8(value) {
this.ensureBufferSizeToWrite(1);
this.view.setInt8(this.pos, value);
this.pos++;
}
writeU16(value) {
this.ensureBufferSizeToWrite(2);
this.view.setUint16(this.pos, value);
this.pos += 2;
}
writeI16(value) {
this.ensureBufferSizeToWrite(2);
this.view.setInt16(this.pos, value);
this.pos += 2;
}
writeU32(value) {
this.ensureBufferSizeToWrite(4);
this.view.setUint32(this.pos, value);
this.pos += 4;
}
writeI32(value) {
this.ensureBufferSizeToWrite(4);
this.view.setInt32(this.pos, value);
this.pos += 4;
}
writeF32(value) {
this.ensureBufferSizeToWrite(4);
this.view.setFloat32(this.pos, value);
this.pos += 4;
}
writeF64(value) {
this.ensureBufferSizeToWrite(8);
this.view.setFloat64(this.pos, value);
this.pos += 8;
}
writeU64(value) {
this.ensureBufferSizeToWrite(8);
(0, int_ts_1.setUint64)(this.view, this.pos, value);
this.pos += 8;
}
writeI64(value) {
this.ensureBufferSizeToWrite(8);
(0, int_ts_1.setInt64)(this.view, this.pos, value);
this.pos += 8;
}
writeBigUint64(value) {
this.ensureBufferSizeToWrite(8);
this.view.setBigUint64(this.pos, value);
this.pos += 8;
}
writeBigInt64(value) {
this.ensureBufferSizeToWrite(8);
this.view.setBigInt64(this.pos, value);
this.pos += 8;
}
}
exports.Encoder = Encoder;
//# sourceMappingURL=Encoder.cjs.map