Advanced tools for manipulating binary data in JavaScript
  • TypeScript 89.3%
  • JavaScript 10.7%
Find a file
2026-05-31 12:25:01 -06:00
.github/workflows Add build-dev workflow 2026-05-10 15:01:21 -06:00
builder Use newer build system 2026-05-10 15:00:00 -06:00
src Use end, not close, listener in adapters 2026-05-31 12:25:01 -06:00
test Add somewhat working tests 2026-02-07 13:00:12 -07:00
.editorconfig Add editorconfig, update todo, and change target to esnext 2026-02-07 13:01:28 -07:00
.gitignore Do not include vscode config in public repositories 2025-08-06 11:33:12 -06:00
.npmignore Add to npmignore 2026-04-20 17:22:30 -06:00
LICENSE Initial commit 2024-10-14 16:46:07 -06:00
package-lock.json Use newer build system 2026-05-10 15:00:00 -06:00
package.json Use newer build system 2026-05-10 15:00:00 -06:00
README.md Adapters for simplestreams to other streaming apis 2026-03-15 11:58:57 -06:00
TODO.md Add editorconfig, update todo, and change target to esnext 2026-02-07 13:01:28 -07:00
tsconfig.json Do a dual-build for esm and commonjs 2026-03-30 16:45:22 -06:00
typedoc.json Bump to 3.0.0 and add typedoc ci support 2025-06-03 14:45:08 -06:00

byteutils

Advanced tools for manipulating binary data in JavaScript

Supported encodings:

  • unsigned integer (bigint and number)
  • signed integer (bigint, number, and one-byte-long optimized function (number), and one-byte-long-for-each-element-array optimized function (number[]))
  • two's complement (bigint, number, and one-byte-long optimized function (number), and one-byte-long-for-each-element-array optimized function (number[]))
  • signed one's complement (bigint, number, and one-byte-long optimized function (number), and one-byte-long-for-each-element-array optimized function (number[]))
  • float (number)
  • double (number)
  • utf8 string
  • mutf8 string (java's string encoding)

Functionality that the buffer module simply can't (the buffer module is not used under the hood). Extendable to interact with your own data pipelines and to easily add your own encodings, supports async and sync data sources (with the same methods in the same class, so you can async or sync data with the same class extensions, see Add your own encoding), implements reading/writing from streams out of the box, and just plain reading or writing binary data to or from a Uint8Array, automaticly resizing and fixed length writableBuffer, and mutch more. See docs

Add your own encoding

Varint

First, find the class you want to extend. For this example, we will be extending readableStream to add minecraft's varint From here. This was mainly to show how to add a variable-length encoding.

import { readableStream, common } from "@chickenjdk/byteutils";
import { PassThrough } from "stream";
export class readableStreamWithVarint extends readableStream {
  /**
   * Parse a minecraft varint
   * @returns The varint
   */
  readVarint() {
    let result = 0;
    let shift = 0;
    const handleByte = (byte) => {
      // loop over every byte
      result |= (byte & 0x7f) << shift; // add the current 7 bits onto the pre-existing result
      shift += 7;
      if (!(byte & 0x80)) {
        // if the continue bit isn't enabled, break.
        return result;
      } else {
        return common.maybePromiseThen(this.shift(), handleByte);
      }
    };
    return common.maybePromiseThen(this.shift(), handleByte);
  }
}
const PassThroughStream = new PassThrough();
const readableStreamInst = new readableStreamWithVarint(PassThroughStream);
const readPairs = [
  [1, [0x1]],
  [25565, [0xdd, 0xc7, 0x01]],
  [1113983, [0xff, 0xfe, 0x43]],
  [-1113983, [0x81,0x81,0xbc,0xff,0xf]],
  [-25565, [0xa3,0xb8,0xfe,0xff,0xf]],
  [-1, [0xff, 0xff, 0xff, 0xff, 0x0f]],
];
(async () => {
  for (const [expected] of readPairs) {
    console.log(
      `Read varint (expected ${expected}): ${await readableStreamInst.readVarint()}`
    );
  }
})();
// 2,147,483,648
for (const [, bytes] of readPairs) {
  PassThroughStream.write(new Uint8Array(bytes));
  await new Promise((resolve) => setTimeout(resolve, 500));
}

Make a Transform stream to convert varints to 32 bit two's complements and re-parse them

import {
  simplestreams,
  writableBufferFixedSize,
} from "@chickenjdk/byteutils";

// Copy + pasted from a random code golf
const encode = (n) => (n >> 7 ? [(n & 127) | 128, ...encode(n >>> 7)] : [n]);

const writableTranslationInst = new writableBufferFixedSize(4);

class ReadableStreamWithVarint extends simplestreams.transform.Transform {
  async pull() {
    let result = 0;
    let shift = 0;
    let done = false;

    while (!done) {
      const byte = await this.source.shift();
      result |= (byte & 0x7f) << shift;
      shift += 7;
      if (!(byte & 0x80)) done = true;
    }

    writableTranslationInst.writeTwosComplement(result, 4);
    const data = writableTranslationInst.buffer;
    writableTranslationInst.reset();
    return data;
  }
}

// Generate data
const numberOfVarints = 500000;

// Generate varints, both positive and negative
const readPairs = Array.from({ length: numberOfVarints }, (_, i) => {
  const value = i % 2 === 0 ? i : -i;
  return [value, encode(value)];
});

console.log(readPairs);
// Actually do it
const source = new simplestreams.pushable.PushableStream(true);
const transform = new ReadableStreamWithVarint(source, true);
const handle = new simplestreams.handles.StreamHandle(transform, true);

for (const [, bytes] of readPairs) {
  await source.source.writeArray(bytes);
}

for (const [expected] of readPairs) {
  const got = await handle.readTwosComplement(4);
  if (got !== expected) {
    throw new Error(`Value mismatch, expected ${expected} but got ${got}`)
  }
}