UNPKG

octify

Version:

Base8 Encoder

207 lines (161 loc) 5.39 kB
const deflate = require("pako"); function error(msg) { throw new Error(`[octify] Error: ${msg}`); } const NUMS = [ "0", "1", "2", "3", "4", "5", "6", "7" ]; const NUMS_VALUES = [ 0b00000000, 0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101, 0b00000110, 0b00000111, ]; const TYPES = [ "string", "object", "number", "bigint", "boolean" ]; const TYPES_VALUES = [ NUMS[0], NUMS[1], NUMS[2], NUMS[3], NUMS[4] ]; const COMPRESSION = [ true, false ]; const COMPRESSION_VALUES = [ NUMS[0], NUMS[1] ]; const text_encoder = new TextEncoder(); const text_decoder = new TextDecoder(); function encode_bytes(bytes, type, compression) { const data = compression ? deflate.deflate(bytes) : bytes; const encoded = []; const compression_index = COMPRESSION.indexOf(compression); const type_index = TYPES.indexOf(type); encoded.push(COMPRESSION_VALUES[compression_index], TYPES_VALUES[type_index]); let equals_next = false; let current_byte, next_byte; const dataLength = data.length; for (let i = 0; i < dataLength; i++) { current_byte = data[i]; next_byte = i < dataLength - 1 ? data[i + 1] : null; if (!equals_next) { encoded.push(NUMS[current_byte >> 5]); } else { equals_next = false; } encoded.push(NUMS[(current_byte >> 2) & 0b00000111]); equals_next = next_byte !== null && next_byte >> 5 === (current_byte & 0b00000011) << 1; if (equals_next) { encoded.push(NUMS[((current_byte & 0b00000011) << 1) | 0b00000001]); } else { encoded.push(NUMS[(current_byte & 0b00000011) << 1]); } } return encoded.join(''); } function decode_bytes(str) { const decoded = []; const compression = COMPRESSION[COMPRESSION_VALUES.indexOf(str[0])]; const type = TYPES[TYPES_VALUES.indexOf(str[1])]; let equals_next = false; let byte; let i = 2; const str_length = str.length; const num_values = NUMS_VALUES; while (i < str_length) { if (equals_next) { byte = (num_values[str[i - 1]] & 0b00000110) << 5 | (num_values[str[i++]] << 2) | (num_values[str[i++]] >> 1); } else { byte = (num_values[str[i++]] << 5) | (num_values[str[i++]] << 2) | (num_values[str[i++]] >> 1); } decoded.push(byte); equals_next = !!(num_values[str[i - 1]] & 0b00000001); } return { bytes : new Uint8Array(compression ? deflate.inflate(decoded) : decoded), type }; } function encode_str(str) { return text_encoder.encode(str); } function decode_str(bytes) { return text_decoder.decode(bytes); } function encode_obj(obj) { return encode_str(JSON.stringify(obj)); } function decode_obj(bytes) { return JSON.parse(decode_str(bytes)); } function encode_num(num) { return encode_str(num.toString()); } function decode_num(bytes) { return Number(decode_str(bytes)); } function encode_bigint(num) { return encode_num(num); } function decode_bigint(bytes) { return BigInt(decode_str(bytes)); } function encode_bool(bool) { return new Uint8Array([ bool ? 1 : 0 ]); } function decode_bool(bytes) { return !!bytes[0]; } /** * Encodes a string or object into a custom compact string format. * Optionally compresses the data using deflate. * * @param {string|object|number|boolean} data - The data to encode. * @param {boolean} [compression=true] - Whether to apply compression. * @returns {string} - The encoded string. * * @example * // Encoding an object with compression (default) * const data = { name: "John", age: 30 }; * const encodedData = encode(data); * * Note: Compressing small-sized data might result in a larger output size than * when uncompressed. * * @example * // Encoding a string with no compression * const data = "Hello World"; * const encodedData = encode(data, false); */ function encode(data, compression = true) { if (data == null || data == undefined) { return data; } const type = typeof data; if (type === "string") { return encode_bytes(encode_str(data), type, compression); } if (type === "object") { return encode_bytes(encode_obj(data), type, compression); } if (type === "number") { return encode_bytes(encode_num(data), type, false); } if (type === "bigint") { return encode_bytes(encode_bigint(data), type, false); } if (type === "boolean") { return encode_bytes(encode_bool(data), type, false); } error(`cannot encode unsupported type '${type}'`); } /** * Decodes a string produced by the encode function back into its original * value. Handles decompression if the original encoding used it. * * @param {string} data - The encoded string. * @returns {string|object|number|boolean} - The decoded original data. */ function decode(data) { if (data == null || data == undefined) { return data; } if (typeof data !== "string") { error(`only string data can be decoded. Encode it first using encode()`); } const {bytes, type} = decode_bytes(data); if (type === "string") { return decode_str(bytes); } if (type === "object") { return decode_obj(bytes); } if (type === "number") { return decode_num(bytes); } if (type === "bigint") { return decode_bigint(bytes); } if (type === "boolean") { return decode_bool(bytes); } error(`decoding failed due to unsupported encoded type '${type}'`); } module.exports = { encode, decode, };