HQTSM: Struct
Binary structures


Features
- Pure TypeScript, run anywhere
- Strong static type checking
- Tree shaking friendly design
- Public interface compatible with
ArrayBufferView
, like native binary types
- Support for little-endian, big-endian, and dynamic endian
- Support for structures, unions, pointers, arrays, inheritance, and more
Usage
Fixed Endianness
Endianness can be defined for each individual member.
import { int8, Struct, uint16BE, uint16LE } from '@hqtsm/struct';
class Example extends Struct {
declare public alpha: number;
declare public beta: number;
declare public gamma: number;
static {
uint16LE(this, 'alpha');
uint16BE(this, 'beta');
int8(this, 'gamma');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.alpha = 0xABCD;
example.beta = 0xBCDE;
example.gamma = -123;
console.assert(data.join(' ') === '205 171 188 222 133');
Dynamic Endianness
Using the endian passed into the constructor, or host endianness.
import { Struct, uint16 } from '@hqtsm/struct';
class Example extends Struct {
declare public alpha: number;
declare public beta: number;
static {
uint16(this, 'alpha');
uint16(this, 'beta');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const exampleLE = new Example(data.buffer, 0, true);
exampleLE.alpha = 0xABCD;
exampleLE.beta = 0xBCDE;
console.assert(data.join(' ') === '205 171 222 188');
const exampleBE = new Example(data.buffer, 0, false);
exampleBE.alpha = 0xABCD;
exampleBE.beta = 0xBCDE;
console.assert(data.join(' ') === '171 205 188 222');
Extending / Inheritance
Structures can be extended with new child members.
import { float32, Struct, uint32 } from '@hqtsm/struct';
class Variable extends Struct {
declare public type: number;
static {
uint32(this, 'type');
}
}
class VariableFloat extends Variable {
declare public value: number;
static {
float32(this, 'value');
}
}
const data = new Uint8Array(VariableFloat.BYTE_LENGTH);
const varFloat = new VariableFloat(data.buffer, 0, true);
varFloat.type = 0xF;
varFloat.value = 3.1415;
console.assert(data.join(' ') === '15 0 0 0 86 14 73 64');
Child Structures
Defining a child structure is easy.
import { Arr, array, member, Struct, uint16BE, Uint8Ptr } from '@hqtsm/struct';
class Child extends Struct {
declare public alpha: number;
declare public beta: number;
static {
uint16BE(this, 'alpha');
uint16BE(this, 'beta');
}
}
class Parent extends Struct {
declare public child1: Child;
declare public child2: Child;
static {
member(Child, this, 'child1');
member(Child, this, 'child2');
}
}
const data = new Uint8Array(Parent.BYTE_LENGTH);
const stru = new Parent(data.buffer);
stru.child1.alpha = 97;
stru.child1.beta = 98;
stru.child2.alpha = 65;
stru.child2.beta = 66;
console.assert(data.join(' ') === '0 97 0 98 0 65 0 66');
Pointer
Comes with pointers for primitives, and a factory for pointers to types.
import { int8, Int8Ptr, pointer, Struct } from '@hqtsm/struct';
const data = new Int8Array(6);
const i8p = new Int8Ptr(data.buffer, 2);
i8p[0] = 0;
i8p[1] = 1;
i8p[2] = 2;
i8p[3] = 3;
i8p[-1] = -1;
i8p[-2] = -2;
console.assert(data.join(' ') === '-2 -1 0 1 2 3');
class XY extends Struct {
declare public x: number;
declare public y: number;
static {
int8(this, 'x');
int8(this, 'y');
}
}
const XYPtr = pointer(XY);
const xyp = new XYPtr(data.buffer, 2);
console.assert(xyp[0].x === 0);
console.assert(xyp[0].y === 1);
console.assert(xyp[1].x === 2);
console.assert(xyp[1].y === 3);
console.assert(xyp[-1].x === -2);
console.assert(xyp[-1].y === -1);
const xy = new XY(new ArrayBuffer(XY.BYTE_LENGTH));
xy.x = 88;
xy.y = 89;
xyp[0] = xy;
console.assert(data.join(' ') === '-2 -1 88 89 2 3');
Array
A pointer extended to a type of a fixed length.
import {
Arr,
array,
int8,
member,
pointer,
Struct,
Uint8Ptr,
} from '@hqtsm/struct';
class XY extends Struct {
declare public x: number;
declare public y: number;
static {
int8(this, 'x');
int8(this, 'y');
}
}
class Example extends Struct {
declare public bytes: Arr<number>;
declare public xys: Arr<XY>;
static {
member(array(Uint8Ptr, 4), this, 'bytes');
member(array(XY, 2), this, 'xys');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.bytes[0] = 10;
example.bytes[1] = 20;
example.bytes[2] = 30;
example.bytes[3] = 40;
example.xys[0].x = 150;
example.xys[0].y = 160;
example.xys[1].x = 170;
example.xys[1].y = 180;
console.assert(data.join(' ') === '10 20 30 40 150 160 170 180');
Union
A union will automatically use overlapping member memory.
import { Arr, array, member, uint32BE, Uint8Ptr, Union } from '@hqtsm/struct';
class FourCC extends Union {
declare public int: number;
declare public chars: Arr<number>;
static {
uint32BE(this, 'int');
member(array(Uint8Ptr, 4), this, 'chars');
}
}
const data = new Uint8Array(FourCC.BYTE_LENGTH);
const four = new FourCC(data.buffer);
four.int = 0x41424344;
console.assert(four.chars[0] === 0x41);
console.assert(String.fromCharCode(...four.chars) === 'ABCD');
Alignment / Padding
By default member memory is sequentially without alignment or padding.
import { pad, Struct, uint32, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
uint32(this, 'beta' as never);
uint8(this, 'gamma');
}
}
console.assert(Example.BYTE_LENGTH === 6);
Padding can be manually added as a property or anonymously.
import { pad, Struct, uint32, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare public padding: never;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
pad(3, this, 'padding');
uint32(this, 'beta' as never);
uint8(this, 'gamma');
pad(3, this);
}
}
console.assert(Example.BYTE_LENGTH === 12);
Using intergers or arrays for padding also works.
Private / Protected
Members can be made private
or protected
but type checking must be relaxed.
Casting the name to never
or any
will pass the type checker.
import { Struct, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
uint8(this, 'beta' as never);
uint8(this, 'gamma');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.setAlpha(65);
example.setBeta(66);
example.gamma = 71;
console.assert(data.join(' ') === '65 66 71');