Skip to content

Tooling to handle binary encoded data in typescript.

Notifications You must be signed in to change notification settings

ViniciusJO/grit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Grit - bit grinder

A set of runtime-agnostic tools to deal with binary encoding and decoding in TypeScript.

This library allows manual handling of bytes or to declarativelly write binary layouts descriptions that automatically:

  • Encodes JavaScript objects → Uint8Array
  • Decodes Uint8Array → strongly‑typed objects
  • Works at bit‑level or byte‑level precision
  • Allows configuration of in/out endianness
  • Shares the same codebase across Bun, Deno and Node.js

No runtime‑specific APIs are required beyond standard Web/JS primitives.


Installation

As a source‑first library, just copy the Bytes.ts file to your project folder and then import:

import { Bytes } from "./Bytes.ts";

Core Concepts

Endianness

All the functions that deal with multiple bytes defaults its input and output endianess to little endian, but are configurable by arguments of type:

type Endianness = "LITTLE" | "BIG";

Type Strings

For use in this libary, binary types are represented as string literals:

type TypeAsString =
    | "bool"
    | "byte"
    | "int"
    | "float"
    | "double"
    | "string"
    | "array"
    | "struct";

They map to concrete TypeScript types:

Binary Type JS Type
bool boolean
byte number (0–255)
int number (int32)
float number (float32)
double number (float64)
string string
array Array<T>
struct object

Bytes Utility

Byte <-> value conversion helpers.

Bytes.from

Signature:

<T extends PrimitiveAsString>(
    type: T,
    out_endianness: Endianness = `LITTLE`
) => (value: PrimitiveTypeReference[T]) => Uint8Array

Encodes a value into a Uint8Array.

const encodeInt = Bytes.from("int");
const buf = encodeInt(123);

Supported types:

type PrimitiveAsString =
    | "bool"
	| "byte"
	| "int"
	| "float"
	| "double"
	| "string";

Bytes.to

Signature:

<T extends PrimitiveAsString>(
    type: T,
    in_endianness: Endianness = `LITTLE`
) => (value: Uint8Array) => PrimitiveTypeReference[T] | void

Decodes a Uint8Array into a JS value.

const decodeInt = Bytes.to("int");
const value = decodeInt(buf);

Bytes.padding

Signature:

(
    size: number,
    in_endianness: Endianness = `LITTLE`,
    out_endianness: Endianness = `LITTLE`
) => (value: Uint8Array) => Uint8Array

Pads a Uint8Array to a fixed size.

const pad8 = Bytes.padding(8);
const padded = pad8(new Uint8Array([1, 2]));

Bytes.toBase64

Signature:

(value: Uint8Array) => string

Encodes binary data to Base64.

const b64 = Bytes.toBase64(buf);

Bit & Byte Manipulation

Bytes.reframe

Signature:

(
    offset: number,
    bits: number,
    in_endianness: Endianness = `LITTLE`,
    out_endianness: Endianness = `LITTLE`
) => (value: Uint8Array) => Uint8Array

Extracts a bit‑range from a Uint8Array.

const slice = Bytes.reframe(3, 5)(buffer);
  • offset → starting bit
  • bits → number of bits to extract

Result is right‑aligned and byte‑packed.


Bytes.strlen

Signature:

(buff: Uint8Array) => number

C‑style string length detection (null‑terminated).

const len = Bytes.strlen(buffer);

Declarative Binary Descriptions

DecodeField

A description of a single binary field.

type DecodeDescription = {
  name: string;
} & (
  | { type: "bool"; bits?: number; bytes?: number }
  | { type: "byte" | "int" | "float" | "double"; bits?: number; bytes?: number }
  | { type: "string"; bytes?: number }
  | { type: "array"; value_description: DecodeDescription; size: number }
  | { type: "struct"; description: DecodeDescription[] }
);

Example description:

const Packet = {
  name: "packet",
  type: "struct",
  description: [
    { name: "id", type: "int", bytes: 4 },
    { name: "temperature", type: "float", bytes: 4 },
    { name: "valid", type: "bool", bits: 1 },
  ],
} as const;

Type Inference

DescribedType<T extends DecodeDescription>

Automatically infers the runtime object type from a binary description.

type PacketType = DescribedType<typeof Packet>;
// {
//   id: number;
//   temperature: number;
//   valid: boolean;
// }

Size Calculation

Bytes.size_in_memory

Signature:

(d: DecodeDescription, value?: Uint8Array) => number

Computes the minimum required size in bytes.

const size = Bytes.size_in_memory(Packet);

Works recursively for arrays and structs.


Encoding

Bytes.encoder

Signature:

<T extends DecodeDescription>(
    desc: T,
    out_endianness: Endianness = `LITTLE`
) => (value: DescribedType<T>) => Uint8Array

Returns a function that encodes structured data into binary.

const encode = Bytes.encoder(Packet);
const buf = encode({ id: 1, temperature: 36.5, valid: true });

Decoding

Bytes.decoder

Signature:

<T extends DecodeDescription>(
    desc: T,
    in_endianness: Endianness = `LITTLE`
) => (value: Uint8Array) => DescribedType<T>

Returns a function that decodes binary data into structured objects.

const decode = Bytes.decoder(Packet);
const value = decode(buf);

Nested Structures

Arrays and structs can be nested arbitrarily.

const Shape = {
  name: "shape",
  type: "struct",
  description: [
    {
      name: "points",
      type: "array",
      size: 2,
      value_description: {
        name: "p",
        type: "struct",
        description: [
          { name: "x", type: "float", bytes: 4 },
          { name: "y", type: "float", bytes: 4 },
        ],
      },
    },
  ],
} as const;

Tested Runtime Compatibility

  • Bun
  • Deno
  • Node

Relies only on:

  • Uint8Array
  • ArrayBuffer
  • DataView
  • TextEncoder / TextDecoder

TODO

  • better methods explanations
  • encoding/decoding not byte aligned structures

About

Tooling to handle binary encoded data in typescript.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published