import type { BitPackerSchema, PackingContext, PackingStrategy, } from "@suaveplan/bitpacker"; import { BITPACKER_CODES, BitpackerError } from "@suaveplan/bitpacker"; /** * Variable-length integer encoding (similar to protobuf varint). * Schema token: `"varint"` for unsigned, `"-varint"` for signed (zigzag). */ export class VarIntPackingStrategy implements PackingStrategy { canHandle(schema: BitPackerSchema): boolean { return schema === "varint" || schema === "-varint"; } pack( data: unknown, schema: BitPackerSchema, context: PackingContext ): void { if (typeof data !== "number" || !Number.isInteger(data)) { throw new BitpackerError( BITPACKER_CODES.INVALID_SCHEMA, `Expected integer for varint schema, got ${typeof data}`, { context: { schema, data } } ); } const signed = schema === "-varint"; let value = signed ? this.#zigzagEncode(data) : data; if (value < 0) { throw new BitpackerError( BITPACKER_CODES.INVALID_SCHEMA, `Unsigned varint cannot be negative: ${value}`, { context: { value } } ); } do { let byte = value & 0x7f; value >>>= 7; if (value !== 0) byte |= 0x80; context.writer.writeUint(byte, 8); } while (value !== 0); } unpack(schema: BitPackerSchema, context: PackingContext): unknown { const signed = schema === "-varint"; let value = 0; let shift = 0; let byte: number; do { byte = context.reader.readUint(8); value |= (byte & 0x7f) << shift; shift += 7; } while ((byte & 0x80) !== 0); return signed ? this.#zigzagDecode(value) : value; } #zigzagEncode(n: number): number { return (n << 1) ^ (n >> 31); } #zigzagDecode(n: number): number { return (n >>> 1) ^ -(n & 1); } }