mirror of
https://github.com/LukeHagar/relay.git
synced 2025-12-06 04:21:14 +00:00
417 lines
14 KiB
JavaScript
417 lines
14 KiB
JavaScript
import { utf8EncodeJs, utf8Count, TEXT_ENCODER_THRESHOLD, utf8EncodeTE } from "./utils/utf8.mjs";
|
|
import { ExtensionCodec } from "./ExtensionCodec.mjs";
|
|
import { setInt64, setUint64 } from "./utils/int.mjs";
|
|
import { ensureUint8Array } from "./utils/typedArrays.mjs";
|
|
export var DEFAULT_MAX_DEPTH = 100;
|
|
export var DEFAULT_INITIAL_BUFFER_SIZE = 2048;
|
|
var Encoder = /** @class */ (function () {
|
|
function Encoder(extensionCodec, context, maxDepth, initialBufferSize, sortKeys, forceFloat32, ignoreUndefined, forceIntegerToFloat) {
|
|
if (extensionCodec === void 0) { extensionCodec = ExtensionCodec.defaultCodec; }
|
|
if (context === void 0) { context = undefined; }
|
|
if (maxDepth === void 0) { maxDepth = DEFAULT_MAX_DEPTH; }
|
|
if (initialBufferSize === void 0) { initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE; }
|
|
if (sortKeys === void 0) { sortKeys = false; }
|
|
if (forceFloat32 === void 0) { forceFloat32 = false; }
|
|
if (ignoreUndefined === void 0) { ignoreUndefined = false; }
|
|
if (forceIntegerToFloat === void 0) { forceIntegerToFloat = false; }
|
|
this.extensionCodec = extensionCodec;
|
|
this.context = context;
|
|
this.maxDepth = maxDepth;
|
|
this.initialBufferSize = initialBufferSize;
|
|
this.sortKeys = sortKeys;
|
|
this.forceFloat32 = forceFloat32;
|
|
this.ignoreUndefined = ignoreUndefined;
|
|
this.forceIntegerToFloat = forceIntegerToFloat;
|
|
this.pos = 0;
|
|
this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
|
|
this.bytes = new Uint8Array(this.view.buffer);
|
|
}
|
|
Encoder.prototype.reinitializeState = function () {
|
|
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.
|
|
*/
|
|
Encoder.prototype.encodeSharedRef = function (object) {
|
|
this.reinitializeState();
|
|
this.doEncode(object, 1);
|
|
return this.bytes.subarray(0, this.pos);
|
|
};
|
|
/**
|
|
* @returns Encodes the object and returns a copy of the encoder's internal buffer.
|
|
*/
|
|
Encoder.prototype.encode = function (object) {
|
|
this.reinitializeState();
|
|
this.doEncode(object, 1);
|
|
return this.bytes.slice(0, this.pos);
|
|
};
|
|
Encoder.prototype.doEncode = function (object, depth) {
|
|
if (depth > this.maxDepth) {
|
|
throw new Error("Too deep objects in depth ".concat(depth));
|
|
}
|
|
if (object == null) {
|
|
this.encodeNil();
|
|
}
|
|
else if (typeof object === "boolean") {
|
|
this.encodeBoolean(object);
|
|
}
|
|
else if (typeof object === "number") {
|
|
this.encodeNumber(object);
|
|
}
|
|
else if (typeof object === "string") {
|
|
this.encodeString(object);
|
|
}
|
|
else {
|
|
this.encodeObject(object, depth);
|
|
}
|
|
};
|
|
Encoder.prototype.ensureBufferSizeToWrite = function (sizeToWrite) {
|
|
var requiredSize = this.pos + sizeToWrite;
|
|
if (this.view.byteLength < requiredSize) {
|
|
this.resizeBuffer(requiredSize * 2);
|
|
}
|
|
};
|
|
Encoder.prototype.resizeBuffer = function (newSize) {
|
|
var newBuffer = new ArrayBuffer(newSize);
|
|
var newBytes = new Uint8Array(newBuffer);
|
|
var newView = new DataView(newBuffer);
|
|
newBytes.set(this.bytes);
|
|
this.view = newView;
|
|
this.bytes = newBytes;
|
|
};
|
|
Encoder.prototype.encodeNil = function () {
|
|
this.writeU8(0xc0);
|
|
};
|
|
Encoder.prototype.encodeBoolean = function (object) {
|
|
if (object === false) {
|
|
this.writeU8(0xc2);
|
|
}
|
|
else {
|
|
this.writeU8(0xc3);
|
|
}
|
|
};
|
|
Encoder.prototype.encodeNumber = function (object) {
|
|
if (Number.isSafeInteger(object) && !this.forceIntegerToFloat) {
|
|
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 {
|
|
// uint 64
|
|
this.writeU8(0xcf);
|
|
this.writeU64(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 {
|
|
// int 64
|
|
this.writeU8(0xd3);
|
|
this.writeI64(object);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// non-integer numbers
|
|
if (this.forceFloat32) {
|
|
// float 32
|
|
this.writeU8(0xca);
|
|
this.writeF32(object);
|
|
}
|
|
else {
|
|
// float 64
|
|
this.writeU8(0xcb);
|
|
this.writeF64(object);
|
|
}
|
|
}
|
|
};
|
|
Encoder.prototype.writeStringHeader = function (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: ".concat(byteLength, " bytes in UTF-8"));
|
|
}
|
|
};
|
|
Encoder.prototype.encodeString = function (object) {
|
|
var maxHeaderSize = 1 + 4;
|
|
var strLength = object.length;
|
|
if (strLength > TEXT_ENCODER_THRESHOLD) {
|
|
var byteLength = utf8Count(object);
|
|
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
|
|
this.writeStringHeader(byteLength);
|
|
utf8EncodeTE(object, this.bytes, this.pos);
|
|
this.pos += byteLength;
|
|
}
|
|
else {
|
|
var byteLength = utf8Count(object);
|
|
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
|
|
this.writeStringHeader(byteLength);
|
|
utf8EncodeJs(object, this.bytes, this.pos);
|
|
this.pos += byteLength;
|
|
}
|
|
};
|
|
Encoder.prototype.encodeObject = function (object, depth) {
|
|
// try to encode objects with custom codec first of non-primitives
|
|
var 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: ".concat(Object.prototype.toString.apply(object)));
|
|
}
|
|
};
|
|
Encoder.prototype.encodeBinary = function (object) {
|
|
var 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: ".concat(size));
|
|
}
|
|
var bytes = ensureUint8Array(object);
|
|
this.writeU8a(bytes);
|
|
};
|
|
Encoder.prototype.encodeArray = function (object, depth) {
|
|
var 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: ".concat(size));
|
|
}
|
|
for (var _i = 0, object_1 = object; _i < object_1.length; _i++) {
|
|
var item = object_1[_i];
|
|
this.doEncode(item, depth + 1);
|
|
}
|
|
};
|
|
Encoder.prototype.countWithoutUndefined = function (object, keys) {
|
|
var count = 0;
|
|
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
|
|
var key = keys_1[_i];
|
|
if (object[key] !== undefined) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
};
|
|
Encoder.prototype.encodeMap = function (object, depth) {
|
|
var keys = Object.keys(object);
|
|
if (this.sortKeys) {
|
|
keys.sort();
|
|
}
|
|
var 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: ".concat(size));
|
|
}
|
|
for (var _i = 0, keys_2 = keys; _i < keys_2.length; _i++) {
|
|
var key = keys_2[_i];
|
|
var value = object[key];
|
|
if (!(this.ignoreUndefined && value === undefined)) {
|
|
this.encodeString(key);
|
|
this.doEncode(value, depth + 1);
|
|
}
|
|
}
|
|
};
|
|
Encoder.prototype.encodeExtension = function (ext) {
|
|
var 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: ".concat(size));
|
|
}
|
|
this.writeI8(ext.type);
|
|
this.writeU8a(ext.data);
|
|
};
|
|
Encoder.prototype.writeU8 = function (value) {
|
|
this.ensureBufferSizeToWrite(1);
|
|
this.view.setUint8(this.pos, value);
|
|
this.pos++;
|
|
};
|
|
Encoder.prototype.writeU8a = function (values) {
|
|
var size = values.length;
|
|
this.ensureBufferSizeToWrite(size);
|
|
this.bytes.set(values, this.pos);
|
|
this.pos += size;
|
|
};
|
|
Encoder.prototype.writeI8 = function (value) {
|
|
this.ensureBufferSizeToWrite(1);
|
|
this.view.setInt8(this.pos, value);
|
|
this.pos++;
|
|
};
|
|
Encoder.prototype.writeU16 = function (value) {
|
|
this.ensureBufferSizeToWrite(2);
|
|
this.view.setUint16(this.pos, value);
|
|
this.pos += 2;
|
|
};
|
|
Encoder.prototype.writeI16 = function (value) {
|
|
this.ensureBufferSizeToWrite(2);
|
|
this.view.setInt16(this.pos, value);
|
|
this.pos += 2;
|
|
};
|
|
Encoder.prototype.writeU32 = function (value) {
|
|
this.ensureBufferSizeToWrite(4);
|
|
this.view.setUint32(this.pos, value);
|
|
this.pos += 4;
|
|
};
|
|
Encoder.prototype.writeI32 = function (value) {
|
|
this.ensureBufferSizeToWrite(4);
|
|
this.view.setInt32(this.pos, value);
|
|
this.pos += 4;
|
|
};
|
|
Encoder.prototype.writeF32 = function (value) {
|
|
this.ensureBufferSizeToWrite(4);
|
|
this.view.setFloat32(this.pos, value);
|
|
this.pos += 4;
|
|
};
|
|
Encoder.prototype.writeF64 = function (value) {
|
|
this.ensureBufferSizeToWrite(8);
|
|
this.view.setFloat64(this.pos, value);
|
|
this.pos += 8;
|
|
};
|
|
Encoder.prototype.writeU64 = function (value) {
|
|
this.ensureBufferSizeToWrite(8);
|
|
setUint64(this.view, this.pos, value);
|
|
this.pos += 8;
|
|
};
|
|
Encoder.prototype.writeI64 = function (value) {
|
|
this.ensureBufferSizeToWrite(8);
|
|
setInt64(this.view, this.pos, value);
|
|
this.pos += 8;
|
|
};
|
|
return Encoder;
|
|
}());
|
|
export { Encoder };
|
|
//# sourceMappingURL=Encoder.mjs.map
|