diff --git a/README.md b/README.md index f743c31..682d29e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ **Byteform** is a lightweight and versatile TypeScript library designed for encoding and decoding binary data. It provides an intuitive API to work with binary structures, making it an excellent choice for developers dealing with low-level data operations in both browser and Node.js environments. +## Table of contents +1. [Why?](#why-byteform) +2. [Features](#features) +3. [Installation](#installation) +4. [Quick Start](#quick-start) + 1. [Create a Binary Structure](#create-a-binary-structure) + 2. [Encode data](#encode-data) + 3. [Decode data](#decode-data) +5. [Contributing](#contributing) + ## Why Byteform? Byteform simplifies binary data handling by providing an intuitive API without sacrificing performance. Whether you're building network protocols, file parsers, or any other application requiring precise binary data manipulation, Byteform has you covered. @@ -11,13 +21,91 @@ Byteform simplifies binary data handling by providing an intuitive API without s - Encode and decode binary data with ease. - Works seamlessly in both browser and Node.js environments. - Built with performance and simplicity in mind. +- Typescript first. - No dependencies. -## Installation (coming soon) +## Installation + +Install Byteform via npm: +```bash +npm install byteform +``` + +Or via yarn: +```bash +yarn add byteform +``` + +## Quick Start + +### Create a Binary Structure + +```typescript +import { Struct, u8, u16, u32, f32, f64 } from 'byteform'; + +const vec3 = new Struct({ + x: f32, + y: f32, + z: f32 +}); + +const bullet = new Struct({ + position: Vec3, + velocity: Vec3, + damage: u8 +}); + +const Player = new Struct({ + name: new Text(32), + level: u8, + position: vec3, + bullets: new List(bullet) +}); +``` + +### Encode data + +```typescript +import { BinaryEncoder } from 'byteform'; + +const encoder = new BinaryEncoder(1024); // 1KB buffer + +// Encode a Player instance +encoder.encode(Player, { + name: 'Alice', + level: 10, + position: { x: 1.0, y: 2.0, z: 3.0 }, + bullets: [ + { + position: { x: 10.0, y: 20.0, z: 30.0 }, + velocity: { x: 1.0, y: 0.0, z: 0.0 }, + damage: 10 + }, + { + position: { x: 20.0, y: 30.0, z: 40.0 }, + velocity: { x: 0.0, y: 1.0, z: 0.0 }, + damage: 20 + } + ] +}); + +/** + * Get the encoded binary data + * Send the buffer over the network, save it to a file, etc. + */ +const buffer = encoder.commit(); +``` + +### Decode data + +```typescript +import { BinaryDecoder } from 'byteform'; -## Usage (coming soon) +const decoder = BinaryDecoder.fromArrayBuffer(buffer); +const player = decoder.decode(Player); -## Documentation (coming soon) +console.log(player); +``` ## Contributing Contributions are welcome! Please open an issue or submit a pull request to get involved. diff --git a/eslint.config.mjs b/eslint.config.mjs index eedc4c2..2233e7d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,8 @@ export default tseslint.config({ "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/explicit-member-accessibility": "error", + "@typescript-eslint/explicit-function-return-type": "error", + semi: "error", indent: ["error", 2] }, diff --git a/package.json b/package.json index 290ddca..485dcd3 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,11 @@ "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", "types": "dist/index.d.ts", + "files": [ + "dist", + "LICENSE", + "README.md" + ], "scripts": { "build": "rollup -c", "test": "jest", diff --git a/src/binary-decoder.ts b/src/binary-decoder.ts index 4f468cb..5bc5c4b 100644 --- a/src/binary-decoder.ts +++ b/src/binary-decoder.ts @@ -1,17 +1,39 @@ import { BufferReader } from "./buffer-reader"; import type { BaseType, InferBaseType } from "./types"; +/** + * A class that provides methods for decoding binary data. + * @group Decoding + */ export class BinaryDecoder { + /** + * The buffer reader. + */ public readonly reader: BufferReader; + /** + * Creates a new binary decoder. + * @param writer - The buffer reader + */ public constructor(writer: BufferReader) { this.reader = writer; } + /** + * Creates a new binary decoder from an ArrayBuffer. + * @param buffer - The ArrayBuffer to decode + * @returns The new binary decoder instance + */ public static fromArrayBuffer(buffer: ArrayBuffer): BinaryDecoder { return new BinaryDecoder(new BufferReader(buffer)); } + /** + * Decodes a value from the buffer. + * @param schema - The schema of the value to decode + * @returns The decoded value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the byte size of the readable schema is larger than the buffer size + */ public decode(schema: T): InferBaseType { return schema.read(this.reader) as InferBaseType; } diff --git a/src/binary-encoder.ts b/src/binary-encoder.ts index 76ba35a..d23a60a 100644 --- a/src/binary-encoder.ts +++ b/src/binary-encoder.ts @@ -1,33 +1,75 @@ +import type { ResizeOptions } from "./buffer-writer"; import { BufferWriter } from "./buffer-writer"; import type { BaseType, InferBaseType } from "./types"; +/** + * A class that provides methods for encoding binary data. + * @group Encoding + */ export class BinaryEncoder { + /** + * The buffer writer. + */ public readonly writer: BufferWriter; + /** + * Creates a new binary encoder. + * @param writer - The buffer writer + */ public constructor(writer: BufferWriter) { this.writer = writer; } - public static create(...params: ConstructorParameters): BinaryEncoder { - return new BinaryEncoder(new BufferWriter(...params)); + /** + * Creates a new binary encoder from a buffer writer. + * @param byteLength - The initial byte length of the buffer + * @param options - {@link BufferWriter} options + * @returns The new binary encoder instance + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the initial buffer size is invalid or the maxByteLength is less than the initial buffer size + */ + public static create(byteLength: number, options: Partial = {}): BinaryEncoder { + return new BinaryEncoder(new BufferWriter(byteLength, options)); } + /** + * Creates a new binary encoder from an ArrayBuffer. + * @param buffer - The ArrayBuffer to encode + * @returns The new binary encoder instance + */ public reset(): void { this.writer.reset(); } + /** + * Encodes a value to the buffer. + * @param schema - The schema of the value to encode + * @param value - The value to encode + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the value is invalid or the value size is larger than the buffer maximum size + */ public encode(schema: T, value: InferBaseType): void { schema.write(value, this.writer); } + /** + * Commits the buffer. + * @returns The ArrayBuffer containing the encoded data + */ public commit(): ArrayBuffer { return this.writer.commit(); } + /** + * Commits the buffer as a Uint8Array. + * @returns The Uint8Array containing the encoded data + */ public commitUint8Array(): Uint8Array { return this.writer.commitUint8Array(); } + /** + * Converts the buffer to an Uint8Array. + * @returns The Uint8Array containing the encoded data + */ public toUint8Array(): Uint8Array { return this.writer.toUint8Array(); } diff --git a/src/buffer-reader.ts b/src/buffer-reader.ts index abe958d..eff0c0b 100644 --- a/src/buffer-reader.ts +++ b/src/buffer-reader.ts @@ -1,62 +1,129 @@ import { BufferView } from "./buffer-view"; +/** + * A class that provides methods to read data from a buffer. + * @group Decoding + */ export class BufferReader extends BufferView { + /** + * Reads an unsigned 8-bit integer from the buffer. + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readUint8(): number { return this._view.getUint8(this._offset++); } + /** + * Reads a signed 8-bit integer from the buffer. + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readInt8(): number { return this._view.getInt8(this._offset++); } + /** + * Reads an unsigned 16-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readUint16(littleEndian: boolean = false): number { const value = this._view.getUint16(this._offset, littleEndian); this._offset += 2; return value; } + /** + * Reads a signed 16-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readInt16(littleEndian: boolean = false): number { const value = this._view.getInt16(this._offset, littleEndian); this._offset += 2; return value; } + /** + * Reads an unsigned 32-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readUint32(littleEndian: boolean = false): number { const value = this._view.getUint32(this._offset, littleEndian); this._offset += 4; return value; } + /** + * Reads a signed 32-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readInt32(littleEndian: boolean = false): number { const value = this._view.getInt32(this._offset, littleEndian); this._offset += 4; return value; } + /** + * Reads an unsigned 64-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readInt64(littleEndian: boolean = false): bigint { const value = this._view.getBigInt64(this._offset, littleEndian); this._offset += 8; return value; } + /** + * Reads a signed 64-bit integer from the buffer. + * @param littleEndian - Whether the integer is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readUint64(littleEndian: boolean = false): bigint { const value = this._view.getBigUint64(this._offset, littleEndian); this._offset += 8; return value; } + /** + * Reads a 32-bit floating point number from the buffer. + * @param littleEndian - Whether the number is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readFloat32(littleEndian: boolean = false): number { const value = this._view.getFloat32(this._offset, littleEndian); this._offset += 4; return value; } + /** + * Reads a 64-bit floating point number from the buffer. + * @param littleEndian - Whether the number is little-endian + * @returns Read value + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readFloat64(littleEndian: boolean = false): number { const value = this._view.getFloat64(this._offset, littleEndian); this._offset += 8; return value; } + /** + * Reads a set of bytes from the buffer. + * @returns An Uint8Array containing the read bytes + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + */ public readBytes(byteLength: number): Uint8Array { const value = new Uint8Array(byteLength); for (let i = 0; i < byteLength; i++) { @@ -65,6 +132,15 @@ export class BufferReader extends BufferView { return value; } + /** + * Reads a set of bytes from the buffer without copying. + * @returns An Uint8Array containing the read bytes + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is out of bounds + * + * @remarks + * The returned Uint8Array shares the same memory as the buffer. + * Use with caution, as modifying the Uint8Array will also modify the buffer. + */ public readBytesUnsafe(byteLength: number): Uint8Array { const value = new Uint8Array(this._buffer, this._offset, byteLength); this._offset += byteLength; diff --git a/src/buffer-view.ts b/src/buffer-view.ts index f3d1580..5a3172f 100644 --- a/src/buffer-view.ts +++ b/src/buffer-view.ts @@ -1,14 +1,32 @@ /** * Base class for reading and writing binary data. + * @group Helpers */ export class BufferView { + /** + * The underlying buffer. + */ protected _buffer: ArrayBuffer; + /** + * The DataView instance to read data from the buffer. + */ protected _view: DataView; + + /** + * The Uint8Array instance to read data from the buffer. + */ protected _u8: Uint8Array; + /** + * The current offset in the buffer in bytes. + */ protected _offset: number; + /** + * Creates a new buffer view. + * @param buffer - The buffer to read/write data from/to + */ public constructor(buffer: ArrayBuffer) { this._buffer = buffer; this._view = new DataView(buffer); @@ -16,30 +34,58 @@ export class BufferView { this._offset = 0; } + /** + * Returns a subarray of the buffer. + * @param start - The start offset in bytes + * @param end - The end offset in bytes + * @returns The subarray + * + * @remarks + * The returned Uint8Array shares the same memory as the buffer. + */ public subarray(start: number, end: number): Uint8Array { return new Uint8Array(this._buffer, start, end - start); } - public seek(offset: number): void { - if (offset < 0 || offset > this._buffer.byteLength) { - throw new RangeError(`BufferView offset is out of bounds: ${offset}, expected a value in a [0, ${this._buffer.byteLength}] range`); + /** + * Seeks to a specific offset in the buffer. + * @param position - The offset to seek to + * @throws RangeError if the position is out of bounds + */ + public seek(position: number): void { + if (position < 0 || position > this._buffer.byteLength) { + throw new RangeError(`BufferView offset is out of bounds: ${position}, expected a value in a [0, ${this._buffer.byteLength}] range`); } - this._offset = offset; + this._offset = position; } + /** + * Skips a number of bytes in the buffer. + * @param offset - The number of bytes to skip + * @throws RangeError if the offset is out of bounds + */ public skip(offset: number): void { this.seek(this._offset + offset); } + /** + * Returns the underlying buffer + */ public get buffer(): ArrayBuffer { return this._buffer; } + /** + * Returns the underlying DataView instance + */ public get view(): DataView { return this._view; } + /** + * Returns the current offset in the buffer in bytes. + */ public get position(): number { return this._offset; } diff --git a/src/buffer-writer.ts b/src/buffer-writer.ts index 819faee..34eef20 100644 --- a/src/buffer-writer.ts +++ b/src/buffer-writer.ts @@ -1,26 +1,65 @@ import { BufferView } from "./buffer-view"; -type ResizeStrategy = 'exponential' | 'additive' | 'hybrid'; - +/** + * The resize strategy for buffer resizing. + * @group Other + */ +export type ResizeStrategy = 'exponential' | 'additive' | 'hybrid'; + +/** + * The options for buffer resizing. + * @group Other + */ export interface ResizeOptions { + /** + * The maximum byte length of the buffer. + * @default undefined + */ maxByteLength?: number; + + /** + * The resize strategy. + * @default 'exponential' + */ strategy: ResizeStrategy; - factor: number; // multiplier - increment: number; // bytes + + /** + * The resize factor for exponential resizing. + * @default 2 + */ + factor: number; + + /** + * The resize increment for additive resizing in bytes. + * @default 256 + */ + increment: number; } +/** + * The function signature for buffer resizing. + */ type ResizeFn = (buffer: ArrayBuffer, options: ResizeOptions) => void; +/** + * The exponential resizing strategy. + */ const ExponentialResize: ResizeFn = (buffer, { factor }) => { const newByteLength = Math.min(Math.round(buffer.byteLength * factor), buffer.maxByteLength); buffer.resize(newByteLength); }; +/** + * The additive resizing strategy. + */ const AdditiveResize: ResizeFn = (buffer, { increment }) => { const newByteLength = Math.min(buffer.byteLength + increment, buffer.maxByteLength); buffer.resize(newByteLength); }; +/** + * The hybrid resizing strategy. + */ const HybridResize: ResizeFn = (buffer, { factor, increment }) => { const newByteLength = Math.min(buffer.byteLength + Math.max(increment, Math.round(buffer.byteLength * (factor - 1))), buffer.maxByteLength); buffer.resize(newByteLength); @@ -32,16 +71,36 @@ const ResizeStrategies: Record = { hybrid: HybridResize }; +/** + * The default options for buffer resizing. + */ const defaultOptions: ResizeOptions = { strategy: 'exponential', factor: 2, - increment: 1024 + increment: 256 }; +/** + * A class that provides methods for writing binary data to a buffer. + * @group Encoding + */ export class BufferWriter extends BufferView { + /** + * The options for buffer resizing. + */ protected _options: ResizeOptions; + + /** + * The function for resizing the buffer. + */ protected _resizeFn: ResizeFn; + /** + * Creates a new buffer writer. + * @param byteLength - The initial byte length of the buffer + * @param options - The options for buffer resizing + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the initial buffer size is invalid or the maxByteLength is less than the initial buffer size + */ public constructor(byteLength: number, options: Partial = {}) { if (byteLength <= 0) { throw new Error(`Invalid initial buffer size: ${byteLength}`); @@ -56,6 +115,11 @@ export class BufferWriter extends BufferView { this._resizeFn = ResizeStrategies[targetOptions.strategy]; } + /** + * Reserves space in the buffer. + * @param byteLength - The number of bytes to reserve + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if the buffer is not resizable and the reserved space exceeds the buffer capacity + */ protected reserve(byteLength: number): void { if (this.remaining < byteLength) { if (!this._buffer.resizable) { @@ -73,90 +137,175 @@ export class BufferWriter extends BufferView { } }; + /** + * The current capacity of the buffer. + */ public get capacity(): number { return this._buffer.byteLength; } + /** + * The remaining space in the buffer in bytes. + */ public get remaining(): number { return this._buffer.byteLength - this._offset; } + /** + * Resets the buffer offset to zero, allowing to write from the beginning. + */ public reset(): void { this._offset = 0; } + /** + * Commits the buffer. + * @returns The ArrayBuffer containing the written data + */ public commit(): ArrayBuffer { return this._buffer.slice(0, this._offset); } + /** + * Commits the buffer as a Uint8Array. + * @returns The Uint8Array containing the written data + */ public commitUint8Array(): Uint8Array { return this._u8.slice(0, this._offset); } + /** + * Converts the buffer to a Uint8Array. + * @returns The Uint8Array containing the written data + */ public toUint8Array(): Uint8Array { return new Uint8Array(this._buffer, 0, this._offset); } + /** + * Writes an unsigned 8-bit integer to the buffer. + * @param value - The value to write + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeUint8(value: number): void { this.reserve(1); this._view.setUint8(this._offset, value); this._offset += 1; } + /** + * Writes a signed 8-bit integer to the buffer. + * @param value - The value to write + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeInt8(value: number): void { this.reserve(1); this._view.setInt8(this._offset, value); this._offset += 1; } + /** + * Writes an unsigned 16-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeUint16(value: number, littleEndian: boolean = false): void { this.reserve(2); this._view.setUint16(this._offset, value, littleEndian); this._offset += 2; } + /** + * Writes a signed 16-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeInt16(value: number, littleEndian: boolean = false): void { this.reserve(2); this._view.setInt16(this._offset, value, littleEndian); this._offset += 2; } + /** + * Writes an unsigned 32-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeUint32(value: number, littleEndian: boolean = false): void { this.reserve(4); this._view.setUint32(this._offset, value, littleEndian); this._offset += 4; } + /** + * Writes a signed 32-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeInt32(value: number, littleEndian: boolean = false): void { this.reserve(4); this._view.setInt32(this._offset, value, littleEndian); this._offset += 4; } + /** + * Writes an unsigned 64-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeInt64(value: bigint, littleEndian: boolean = false): void { this.reserve(8); this._view.setBigInt64(this._offset, value, littleEndian); this._offset += 8; } + /** + * Writes an unsigned 64-bit integer to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the integer is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeUint64(value: bigint, littleEndian: boolean = false): void { this.reserve(8); this._view.setBigUint64(this._offset, value, littleEndian); this._offset += 8; } + /** + * Writes a 32-bit float to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the float is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeFloat32(value: number, littleEndian: boolean = false): void { this.reserve(4); this._view.setFloat32(this._offset, value, littleEndian); this._offset += 4; } + /** + * Writes a 64-bit float to the buffer. + * @param value - The value to write + * @param littleEndian - Whether the float is little-endian + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeFloat64(value: number, littleEndian: boolean = false): void { this.reserve(8); this._view.setFloat64(this._offset, value, littleEndian); this._offset += 8; } + /** + * Writes bytes to the buffer. + * @param src - The source buffer to write from + * @param byteLength - The number of bytes to write + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} if buffer is full and not resizable or has reached its maximum capacity + */ public writeBytes(src: Uint8Array, byteLength?: number): void { const length = byteLength ?? src.byteLength; this.reserve(length); diff --git a/src/types/base.ts b/src/types/base.ts index 62a8586..aa40d81 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -1,11 +1,42 @@ import type { BufferReader } from "../buffer-reader"; import type { BufferWriter } from "../buffer-writer"; +/** + * Base type for all data types. + * @group Helpers + * + * @typeParam T - The type of the data to encode and decode. + */ export interface BaseType { + /** + * Writes the value to the buffer. + * @param value - The value to write. + * @param writer - The buffer writer. + */ write(value: T, writer: BufferWriter): void; + + /** + * Reads the value from the buffer. + * @param reader - The buffer reader. + * @returns The value read from the buffer. + */ read(reader: BufferReader): T; } +/** + * Infer the data type from a schema. + * @group Helpers + * + * @typeParam T - The schema to infer the data type from. + */ export type InferBaseType = T extends BaseType ? U : never; +/** + * Utility function to create a type descriptor. + * @group Helpers + * + * @param descriptor - The type descriptor. + * @typeParam T - The type of the schema. + * @returns The schema type. + */ export const createType = (descriptor: BaseType): Readonly> => descriptor; diff --git a/src/types/list.ts b/src/types/list.ts index f85c26d..9e96b6f 100644 --- a/src/types/list.ts +++ b/src/types/list.ts @@ -2,18 +2,39 @@ import type { BufferReader } from "../buffer-reader"; import type { BufferWriter } from "../buffer-writer"; import type { BaseType } from "./base"; +/** + * A type that represents a list of items of a specific type. Similar to an array in JavaScript. + * @group Types + * + * @typeParam T - The type of the items in the list. + */ export class List implements BaseType { + /** + * The type of the items in the list. + */ private _of: BaseType; + /** + * Creates a new list type. + * @param of - The type of the items in the list. + */ public constructor(of: BaseType) { this._of = of; } - public get of() { + /** + * Gets the base type of the items in the list. + */ + public get of(): BaseType { return this._of; } - public write(value: T[], writer: BufferWriter) { + /** + * Writes the list to the buffer. + * @param value - An array of items to write. + * @param writer - The buffer writer. + */ + public write(value: T[], writer: BufferWriter): void { writer.writeUint32(value.length); for (const item of value) { @@ -21,6 +42,11 @@ export class List implements BaseType { } } + /** + * Reads the list from the buffer. + * @param reader - The buffer reader. + * @returns An array of items read from the buffer. + */ public read(reader: BufferReader): T[] { const length = reader.readUint32(); const value = new Array(length); diff --git a/src/types/numbers.ts b/src/types/numbers.ts index fac71c6..a5592c3 100644 --- a/src/types/numbers.ts +++ b/src/types/numbers.ts @@ -1,100 +1,211 @@ import { createType } from "./base"; +/** + * A type that represents an unsigned 8-bit integer. + * @group Available Types + */ export const u8 = createType({ write: (value, writer) => writer.writeUint8(value), read: (reader) => reader.readUint8(), }); +/** + * A type that represents a signed 8-bit integer. + * @group Available Types + */ export const i8 = createType({ write: (value, writer) => writer.writeInt8(value), read: (reader) => reader.readInt8(), }); +/** + * A type that represents an unsigned 16-bit integer (little-endian). + * @group Available Types + */ export const u16le = createType({ write: (value, writer) => writer.writeUint16(value, true), read: (reader) => reader.readUint16(true), }); +/** + * A type that represents an unsigned 16-bit integer (big-endian). + * @group Available Types + */ export const u16be = createType({ write: (value, writer) => writer.writeUint16(value), read: (reader) => reader.readUint16(), }); +/** + * A type that represents a signed 16-bit integer (little-endian). + * @group Available Types + */ export const i16le = createType({ write: (value, writer) => writer.writeInt16(value, true), read: (reader) => reader.readInt16(true), }); +/** + * A type that represents a signed 16-bit integer (big-endian). + * @group Available Types + */ export const i16be = createType({ write: (value, writer) => writer.writeInt16(value), read: (reader) => reader.readInt16(), }); +/** + * A type that represents an unsigned 32-bit integer (little-endian). + * @group Available Types + */ export const u32le = createType({ write: (value, writer) => writer.writeUint32(value, true), read: (reader) => reader.readUint32(true), }); +/** + * A type that represents an unsigned 32-bit integer (big-endian). + * @group Available Types + */ export const u32be = createType({ write: (value, writer) => writer.writeUint32(value), read: (reader) => reader.readUint32(), }); +/** + * A type that represents a signed 32-bit integer (little-endian). + * @group Available Types + */ export const i32le = createType({ write: (value, writer) => writer.writeInt32(value, true), read: (reader) => reader.readInt32(true), }); +/** + * A type that represents a signed 32-bit integer (big-endian). + * @group Available Types + */ export const i32be = createType({ write: (value, writer) => writer.writeInt32(value), read: (reader) => reader.readInt32(), }); +/** + * A type that represents an unsigned 64-bit integer (little-endian). + * @group Available Types + */ export const u64le = createType({ write: (value, writer) => writer.writeUint64(value, true), read: (reader) => reader.readUint64(true), }); +/** + * A type that represents an unsigned 64-bit integer (big-endian). + * @group Available Types + */ export const u64be = createType({ write: (value, writer) => writer.writeUint64(value), read: (reader) => reader.readUint64(), }); +/** + * A type that represents a signed 64-bit integer (little-endian). + * @group Available Types + */ export const i64le = createType({ write: (value, writer) => writer.writeInt64(value, true), read: (reader) => reader.readInt64(true), }); +/** + * A type that represents a signed 64-bit integer (big-endian). + * @group Available Types + */ export const i64be = createType({ write: (value, writer) => writer.writeInt64(value), read: (reader) => reader.readInt64(), }); +/** + * A type that represents a 32-bit floating-point number (little-endian). + * @group Available Types + */ export const f32le = createType({ write: (value, writer) => writer.writeFloat32(value, true), read: (reader) => reader.readFloat32(true), }); +/** + * A type that represents a 32-bit floating-point number (big-endian). + * @group Available Types + */ export const f32be = createType({ write: (value, writer) => writer.writeFloat32(value), read: (reader) => reader.readFloat32(), }); +/** + * A type that represents a 64-bit floating-point number (little-endian). + * @group Available Types + */ export const f64le = createType({ write: (value, writer) => writer.writeFloat64(value, true), read: (reader) => reader.readFloat64(true), }); +/** + * A type that represents a 64-bit floating-point number (big-endian). + * @group Available Types + */ export const f64be = createType({ write: (value, writer) => writer.writeFloat64(value), read: (reader) => reader.readFloat64(), }); +/** + * Type alias for {@link u16be}. Which is an unsigned 16-bit integer (big-endian). + * @group Available Types + */ export const u16 = u16be; + +/** + * Type alias for {@link i16be}. Which is a signed 16-bit integer (big-endian). + * @group Available Types + */ export const i16 = i16be; + +/** + * Type alias for {@link u32be}. Which is an unsigned 32-bit integer (big-endian). + * @group Available Types + */ export const u32 = u32be; + +/** + * Type alias for {@link i32be}. Which is a signed 32-bit integer (big-endian). + * @group Available Types + */ export const i32 = i32be; + +/** + * Type alias for {@link u64be}. Which is an unsigned 64-bit integer (big-endian). + * @group Available Types + */ export const u64 = u64be; + +/** + * Type alias for {@link i64be}. Which is a signed 64-bit integer (big-endian). + * @group Available Types + */ export const i64 = i64be; + +/** + * Type alias for {@link f32be}. Which is a 32-bit floating-point number (big-endian). + * @group Available Types + */ export const f32 = f32be; + +/** + * Type alias for {@link f64be}. Which is a 64-bit floating-point number (big-endian). + * @group Available Types + */ export const f64 = f64be; diff --git a/src/types/struct.ts b/src/types/struct.ts index c433959..2de338d 100644 --- a/src/types/struct.ts +++ b/src/types/struct.ts @@ -6,19 +6,42 @@ type StructTypes = { [K in keyof T]: BaseType; } +/** + * A type that represents a structure of multiple fields. Similar to an object in JavaScript. + * @group Types + * + * @typeParam T - The type of the structure where each key is a field name and the value is the type of the field. + */ export class Struct implements BaseType { + /** + * The entries of the structure. + */ private entries: [string, BaseType][] = []; + /** + * Creates a new structure type. + * @param entries - The entries of the structure where each key is a field name and the value is the type of the field. + */ public constructor(entries: StructTypes) { this.entries = Object.entries(entries); } - public write(value: T, writer: BufferWriter) { + /** + * Writes the structure to the buffer. + * @param value - The structure to write. + * @param writer - The buffer writer. + */ + public write(value: T, writer: BufferWriter): void { for (const [key, type] of this.entries) { type.write(value[key], writer); } } + /** + * Reads the structure from the buffer. + * @param reader - The buffer reader. + * @returns The structure read from the buffer. + */ public read(reader: BufferReader): T { const value = {} as T; diff --git a/src/types/text.ts b/src/types/text.ts index 18c921f..448a568 100644 --- a/src/types/text.ts +++ b/src/types/text.ts @@ -2,18 +2,45 @@ import type { BufferReader } from "../buffer-reader"; import type { BufferWriter } from "../buffer-writer"; import type { BaseType } from "./base"; -// https://nodejs.org/api/globals.html#textencoder +/** + * @see [TextEncoder](https://nodejs.org/api/globals.html#textencoder) + * Since TextEncoder and TextDecoder are defined globally in Node.js starting from version 11.0.0, we are going to specify it as the minimum supported version. + */ + +/** + * TextEncoder instance to encode strings to bytes. + */ const Encoder = new TextEncoder(); + +/** + * TextDecoder instance to decode strings from bytes. + */ const Decoder = new TextDecoder(); +/** + * A type that represents a string of text. + * @group Types + */ export class Text implements BaseType { + /** + * The intermediate buffer to store the encoded string before writing it to the buffer. + */ private stringBuffer: Uint8Array; + /** + * Creates a new text type. + * @param maxByteLength - The maximum byte length of the encoded string + */ public constructor(maxByteLength: number = 256) { this.stringBuffer = new Uint8Array(maxByteLength); } - public write(value: string, writer: BufferWriter) { + /** + * Writes the string to the buffer. + * @param value - The string to write. + * @param writer - The buffer writer. + */ + public write(value: string, writer: BufferWriter): void { const res = Encoder.encodeInto(value, this.stringBuffer); if (res.read !== value.length) { @@ -24,6 +51,11 @@ export class Text implements BaseType { writer.writeBytes(this.stringBuffer, res.written); } + /** + * Reads the string from the buffer. + * @param reader - The buffer reader. + * @returns The string read from the buffer. + */ public read(reader: BufferReader): string { const length = reader.readUint32(); const offset = reader.position; @@ -33,4 +65,8 @@ export class Text implements BaseType { } } +/** + * A type that represents a string of text with a maximum byte length of 256. + * @group Available Types + */ export const text = new Text(); diff --git a/tsdoc.json b/tsdoc.json new file mode 100644 index 0000000..0d5c221 --- /dev/null +++ b/tsdoc.json @@ -0,0 +1,179 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "noStandardTags": false, + "tagDefinitions": [ + { + "tagName": "@author", + "syntaxKind": "block" + }, + { + "tagName": "@module", + "syntaxKind": "block" + }, + { + "tagName": "@type", + "syntaxKind": "block" + }, + { + "tagName": "@typedef", + "syntaxKind": "block" + }, + { + "tagName": "@callback", + "syntaxKind": "block" + }, + { + "tagName": "@prop", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@property", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@group", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@groupDescription", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@category", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@categoryDescription", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@hidden", + "syntaxKind": "modifier" + }, + { + "tagName": "@ignore", + "syntaxKind": "modifier" + }, + { + "tagName": "@class", + "syntaxKind": "modifier" + }, + { + "tagName": "@abstract", + "syntaxKind": "modifier" + }, + { + "tagName": "@document", + "syntaxKind": "block" + }, + { + "tagName": "@default", + "syntaxKind": "block" + }, + { + "tagName": "@extends", + "syntaxKind": "block" + }, + { + "tagName": "@augments", + "syntaxKind": "block" + }, + { + "tagName": "@return", + "syntaxKind": "block" + }, + { + "tagName": "@yields", + "syntaxKind": "block" + }, + { + "tagName": "@enum", + "syntaxKind": "modifier" + }, + { + "tagName": "@event", + "syntaxKind": "modifier" + }, + { + "tagName": "@template", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@linkcode", + "syntaxKind": "inline", + "allowMultiple": true + }, + { + "tagName": "@linkplain", + "syntaxKind": "inline", + "allowMultiple": true + }, + { + "tagName": "@private", + "syntaxKind": "modifier" + }, + { + "tagName": "@protected", + "syntaxKind": "modifier" + }, + { + "tagName": "@satisfies", + "syntaxKind": "block" + }, + { + "tagName": "@since", + "syntaxKind": "block" + }, + { + "tagName": "@license", + "syntaxKind": "block" + }, + { + "tagName": "@import", + "syntaxKind": "block" + }, + { + "tagName": "@overload", + "syntaxKind": "modifier" + }, + { + "tagName": "@namespace", + "syntaxKind": "modifier" + }, + { + "tagName": "@interface", + "syntaxKind": "modifier" + }, + { + "tagName": "@showCategories", + "syntaxKind": "modifier" + }, + { + "tagName": "@hideCategories", + "syntaxKind": "modifier" + }, + { + "tagName": "@showGroups", + "syntaxKind": "modifier" + }, + { + "tagName": "@hideGroups", + "syntaxKind": "modifier" + }, + { + "tagName": "@hideconstructor", + "syntaxKind": "modifier" + }, + { + "tagName": "@jsx", + "syntaxKind": "block" + } + ] +}