CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-flatbuffers

Memory efficient cross-platform serialization library with zero-copy deserialization supporting 15+ programming languages.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

javascript.mddocs/

JavaScript/TypeScript

JavaScript and TypeScript implementation of FlatBuffers providing browser and Node.js compatibility with zero-copy deserialization benefits adapted for JavaScript's memory model and garbage collection.

Capabilities

Installation and Imports

Install FlatBuffers for JavaScript projects and import the necessary components.

# Installation
npm install flatbuffers

# Or with yarn
yarn add flatbuffers
// ES6/TypeScript imports
import { Builder, ByteBuffer } from 'flatbuffers';

// CommonJS imports  
const { Builder, ByteBuffer } = require('flatbuffers');

// Individual imports
import { Builder } from 'flatbuffers';
import { ByteBuffer } from 'flatbuffers';
import { Encoding } from 'flatbuffers';

// FlexBuffers imports
import * as flexbuffers from 'flatbuffers/flexbuffers';

Builder Class

Main builder class for constructing FlatBuffer data in JavaScript/TypeScript environments.

class Builder {
  /**
   * Create a new FlatBuffer builder
   * @param initialSize Initial buffer size in bytes (default: 1024)
   */
  constructor(initialSize?: number);

  /** Reset the builder for reuse */
  clear(): void;

  /** Get current buffer offset */
  offset(): number;

  /** Get the ByteBuffer representing the FlatBuffer */
  dataBuffer(): ByteBuffer;

  /** Get the finished buffer as Uint8Array */
  asUint8Array(): Uint8Array;

  /** Control default value serialization */
  forceDefaults(forceDefaults: boolean): void;

  // String and byte vector creation
  /** Create a string and return its offset */
  createString(s: string | Uint8Array | null | undefined): number;
  
  /** Create a shared string (with deduplication) and return its offset */
  createSharedString(s: string | Uint8Array): number;
  
  /** Create a byte vector and return its offset */
  createByteVector(v: Uint8Array | null | undefined): number;

  // Object helper methods
  /** Create offset for an object (string or IGeneratedObject) */
  createObjectOffset(obj: string | IGeneratedObject | null): number;
  
  /** Create offsets for a list of objects */
  createObjectOffsetList(list: (string | IGeneratedObject)[]): number[];
  
  /** Create a vector from a list of objects */
  createStructOffsetList(list: (string | IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void): number;

  // Vector building
  /** Start building a vector */
  startVector(elemSize: number, numElems: number, alignment: number): void;

  /** End vector construction and return offset */
  endVector(): number;

  // Table/Object building
  /** Start building a table/object */
  startObject(numFields: number): void;

  /** End table construction and return offset */
  endObject(): number;

  // Primitive value methods
  /** Add primitive values to the buffer */
  addInt8(value: number): void;
  addInt16(value: number): void;
  addInt32(value: number): void;
  addInt64(value: bigint): void;
  addFloat32(value: number): void;
  addFloat64(value: number): void;

  /** Write primitive values to buffer at current space */
  writeInt8(value: number): void;
  writeInt16(value: number): void;
  writeInt32(value: number): void;
  writeInt64(value: bigint): void;
  writeFloat32(value: number): void;
  writeFloat64(value: number): void;

  // Table field methods
  /** Add field to current table */
  addFieldInt8(voffset: number, value: number, defaultValue: number | null): void;
  addFieldInt16(voffset: number, value: number, defaultValue: number | null): void;
  addFieldInt32(voffset: number, value: number, defaultValue: number | null): void;
  addFieldInt64(voffset: number, value: bigint, defaultValue: bigint | null): void;
  addFieldFloat32(voffset: number, value: number, defaultValue: number | null): void;
  addFieldFloat64(voffset: number, value: number, defaultValue: number | null): void;
  addFieldOffset(voffset: number, value: number, defaultValue: number): void;

  /** Add struct field to current table */
  addFieldStruct(voffset: number, value: number, defaultValue: number): void;

  // Buffer management
  /** Prepare buffer for writing */
  prep(size: number, additionalBytes: number): void;
  
  /** Add padding bytes */
  pad(byteSize: number): void;
  
  /** Add an offset value */
  addOffset(offset: number): void;

  /** Finish the buffer with root table */
  finish(rootTable: number, fileIdentifier?: string, sizePrefix?: boolean): void;

  /** Finish buffer with size prefix */
  finishSizePrefixed(rootTable: number, fileIdentifier?: string): void;

  /** Check if a required field has been set */
  requiredField(table: number, field: number): void;

  /** Validate nested object serialization */
  nested(obj: number): void;

  /** Check that no nested object is being serialized */
  notNested(): void;

  /** Set vtable slot value */
  slot(voffset: number): void;

  /** Grow ByteBuffer capacity */
  static growByteBuffer(bb: ByteBuffer): ByteBuffer;
}

Usage Example:

import { Builder } from 'flatbuffers';

// Create builder
const builder = new Builder(1024);

// Create string
const nameOffset = builder.createString("Player");

// Create vector  
const scores = [100, 200, 300];
builder.startVector(4, scores.length, 4);
for (let i = scores.length - 1; i >= 0; i--) {
  builder.addInt32(scores[i]);
}
const scoresOffset = builder.endVector();

// Create table
builder.startObject(3);
builder.addFieldOffset(0, nameOffset, 0);     // name field
builder.addFieldInt32(1, 42, 0);              // level field  
builder.addFieldOffset(2, scoresOffset, 0);   // scores field
const player = builder.endObject();

// Finish buffer
builder.finish(player);

// Get binary data
const buffer = builder.asUint8Array();

ByteBuffer Class

Wrapper for reading FlatBuffer data from binary buffers, providing typed access methods.

class ByteBuffer {
  /**
   * Create ByteBuffer from Uint8Array
   * @param bytes Binary data to wrap
   */
  constructor(bytes: Uint8Array);

  /** Create and allocate a new ByteBuffer with given size */
  static allocate(byteSize: number): ByteBuffer;

  /** Clear the buffer and reset position */
  clear(): void;

  /** Get the underlying Uint8Array */
  bytes(): Uint8Array;

  /** Get current read/write position */
  position(): number;

  /** Set read/write position */
  setPosition(position: number): void;

  /** Get buffer capacity */
  capacity(): number;

  // Read methods
  /** Read 8-bit integers */
  readInt8(offset: number): number;
  readUint8(offset: number): number;

  /** Read 16-bit integers */
  readInt16(offset: number): number; 
  readUint16(offset: number): number;
  
  /** Read 32-bit integers */
  readInt32(offset: number): number;
  readUint32(offset: number): number;

  /** Read 64-bit integers */
  readInt64(offset: number): bigint;
  readUint64(offset: number): bigint;

  /** Read floating point numbers */
  readFloat32(offset: number): number;
  readFloat64(offset: number): number;

  // Write methods
  /** Write 8-bit integers */
  writeInt8(offset: number, value: number): void;
  writeUint8(offset: number, value: number): void;

  /** Write 16-bit integers */
  writeInt16(offset: number, value: number): void;
  writeUint16(offset: number, value: number): void;

  /** Write 32-bit integers */
  writeInt32(offset: number, value: number): void;
  writeUint32(offset: number, value: number): void;

  /** Write 64-bit integers */
  writeInt64(offset: number, value: bigint): void;
  writeUint64(offset: number, value: bigint): void;

  /** Write floating point numbers */
  writeFloat32(offset: number, value: number): void;
  writeFloat64(offset: number, value: number): void;

  // FlatBuffer-specific methods
  /** Get buffer file identifier */
  getBufferIdentifier(): string;

  /** Check if buffer has specific identifier */
  __has_identifier(ident: string): boolean;

  /** Look up field in vtable */
  __offset(bbPos: number, vtableOffset: number): number;

  /** Read string at offset */
  __string(offset: number, encoding?: Encoding): string | Uint8Array;

  /** Initialize union table */
  __union(t: Table, offset: number): Table;

  /** Handle union with string member */
  __union_with_string(o: Table | string, offset: number): Table | string;

  /** Get indirect offset */
  __indirect(offset: number): number;

  /** Get vector data start offset */
  __vector(offset: number): number;

  /** Get vector length */
  __vector_len(offset: number): number;

  // Helper methods for object API
  /** Create scalar list from accessor function */
  createScalarList<T>(listAccessor: (i: number) => T | null, listLength: number): T[];

  /** Create object list from accessor function */
  createObjList<T1 extends IUnpackableObject<T2>, T2 extends IGeneratedObject>(
    listAccessor: (i: number) => T1 | null, 
    listLength: number
  ): T2[];
}

Type Definitions

TypeScript type definitions for better development experience and type safety.

// Core type definitions

export type Offset = number;

export type Table = {
  bb: ByteBuffer;
  bb_pos: number;
};

export interface IGeneratedObject {
  pack(builder: Builder): Offset;
}

export interface IUnpackableObject<T> {
  unpack(): T;
}

// Encoding enum for string handling
export enum Encoding {
  UTF8_BYTES = 1,
  UTF16_STRING = 2
}

// Constants (typed arrays, not functions)
export const SIZEOF_SHORT: number = 2;
export const SIZEOF_INT: number = 4; 
export const FILE_IDENTIFIER_LENGTH: number = 4;
export const SIZE_PREFIX_LENGTH: number = 4;

// Utility typed arrays for float/int conversion
export const int32: Int32Array;
export const float32: Float32Array;
export const float64: Float64Array;
export const isLittleEndian: boolean;

FlexBuffers

FlexBuffers provides a more flexible, schemaless alternative to FlatBuffers with dynamic typing and JSON-like data structures.

// FlexBuffers main API
export function builder(): FlexBuffersBuilder;
export function encode(object: unknown, size?: number, deduplicateStrings?: boolean, deduplicateKeys?: boolean, deduplicateKeyVectors?: boolean): Uint8Array;
export function toObject(buffer: ArrayBuffer): unknown;
export function toReference(buffer: ArrayBuffer): Reference;

// FlexBuffers Builder
class FlexBuffersBuilder {
  constructor(size?: number, dedupStrings?: boolean, dedupKeys?: boolean, dedupKeyVectors?: boolean);

  // Value addition methods
  add(value: undefined | null | boolean | bigint | number | DataView | string | Array<unknown> | Record<string, unknown> | unknown): void;
  addKey(key: string): void;
  addInt(value: number, indirect?: boolean, deduplicate?: boolean): void;
  addUInt(value: number, indirect?: boolean, deduplicate?: boolean): void;
  addFloat(value: number, indirect?: boolean, deduplicate?: boolean): void;

  // Container methods
  startVector(): void;
  startMap(presorted?: boolean): void;
  end(): void;

  // Finishing
  finish(): Uint8Array;
  isFinished(): boolean;

  // Buffer management
  computeOffset(newValueSize: number): number;
  pushInt(value: number, width: BitWidth): void;
  pushUInt(value: number, width: BitWidth): void;
}

// FlexBuffers Reference for reading
class Reference {
  // Type checking methods
  isNull(): boolean;
  isNumber(): boolean;
  isFloat(): boolean;
  isInt(): boolean;
  isString(): boolean;
  isBool(): boolean;
  isBlob(): boolean;
  isVector(): boolean;
  isMap(): boolean;

  // Value extraction methods
  boolValue(): boolean | null;
  intValue(): number | bigint | null;
  floatValue(): number | null;
  numericValue(): number | bigint | null;
  stringValue(): string | null;
  blobValue(): Uint8Array | null;

  // Container access
  get(key: number | string): Reference;
  length(): number;
  
  // Convert to JavaScript object
  toObject(): unknown;
}

// BitWidth enum for FlexBuffers
enum BitWidth {
  WIDTH8 = 0,
  WIDTH16 = 1,
  WIDTH32 = 2,
  WIDTH64 = 3
}

FlexBuffers Usage Example:

import * as flexbuffers from 'flatbuffers/flexbuffers';

// Create data using the high-level encode function
const data = {
  name: "Monster",
  hp: 100,
  inventory: [1, 2, 3, 4, 5],
  position: { x: 1.0, y: 2.0, z: 3.0 }
};

// Encode to FlexBuffer
const buffer = flexbuffers.encode(data);

// Decode from FlexBuffer
const decoded = flexbuffers.toObject(buffer.buffer);
console.log(decoded);

// Using the builder for more control
const builder = flexbuffers.builder();
builder.startMap();
  builder.addKey("name");
  builder.add("Monster");
  builder.addKey("hp");
  builder.add(100);
  builder.addKey("inventory");
  builder.startVector();
    builder.add(1);
    builder.add(2);
    builder.add(3);
  builder.end();
builder.end();

const flexBuffer = builder.finish();

// Using Reference for reading
const ref = flexbuffers.toReference(flexBuffer.buffer);
console.log(ref.get("name").stringValue()); // "Monster"
console.log(ref.get("hp").intValue());       // 100
console.log(ref.get("inventory").length());  // 3

Generated Code Structure

When using flatc --ts or flatc --js, the compiler generates TypeScript or JavaScript classes following these patterns.

// Example generated TypeScript (from monster.fbs):

import { Builder, ByteBuffer, Offset } from 'flatbuffers';

export class Vec3 {
  constructor(
    public x: number = 0,
    public y: number = 0, 
    public z: number = 0
  ) {}

  static pack(builder: Builder, obj: Vec3 | null): Offset {
    if (obj === null) return 0;
    builder.prep(4, 12);
    builder.writeFloat32(obj.z);
    builder.writeFloat32(obj.y);
    builder.writeFloat32(obj.x);
    return builder.offset();
  }
}

export class Monster {
  bb: ByteBuffer | null = null;
  bb_pos: number = 0;

  __init(i: number, bb: ByteBuffer): Monster {
    this.bb_pos = i;
    this.bb = bb;
    return this;
  }

  static getRootAsMonster(bb: ByteBuffer, obj?: Monster): Monster {
    return (obj || new Monster()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
  }

  pos(obj?: Vec3): Vec3 | null {
    const offset = this.bb!.__offset(this.bb_pos, 4);
    return offset ? (obj || new Vec3()).__assign(
      this.bb!.readFloat32(this.bb_pos + offset),
      this.bb!.readFloat32(this.bb_pos + offset + 4),
      this.bb!.readFloat32(this.bb_pos + offset + 8)
    ) : null;
  }

  mana(): number {
    const offset = this.bb!.__offset(this.bb_pos, 6);
    return offset ? this.bb!.readInt16(this.bb_pos + offset) : 150;
  }

  hp(): number {
    const offset = this.bb!.__offset(this.bb_pos, 8);
    return offset ? this.bb!.readInt16(this.bb_pos + offset) : 100;
  }

  name(): string | null {
    const offset = this.bb!.__offset(this.bb_pos, 10);
    return offset ? this.bb!.__string(this.bb_pos + offset) : null;
  }

  inventory(index: number): number {
    const offset = this.bb!.__offset(this.bb_pos, 14);
    return offset ? this.bb!.readUint8(this.bb!.__vector(this.bb_pos + offset) + index) : 0;
  }

  inventoryLength(): number {
    const offset = this.bb!.__offset(this.bb_pos, 14);
    return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
  }

  static startMonster(builder: Builder): void {
    builder.startObject(6);
  }

  static addPos(builder: Builder, pos: Offset): void {
    builder.addFieldStruct(0, pos, 0);
  }

  static addMana(builder: Builder, mana: number): void {
    builder.addFieldInt16(1, mana, 150);
  }

  static addHp(builder: Builder, hp: number): void {
    builder.addFieldInt16(2, hp, 100);
  }

  static addName(builder: Builder, name: Offset): void {
    builder.addFieldOffset(3, name, 0);
  }

  static addInventory(builder: Builder, inventory: Offset): void {
    builder.addFieldOffset(5, inventory, 0);
  }

  static createInventoryVector(builder: Builder, data: number[]): Offset {
    builder.startVector(1, data.length, 1);
    for (let i = data.length - 1; i >= 0; i--) {
      builder.addInt8(data[i]!);
    }
    return builder.endVector();
  }

  static endMonster(builder: Builder): Offset {
    return builder.endObject();
  }

  static createMonster(
    builder: Builder,
    pos: Offset,
    mana: number,
    hp: number, 
    name: Offset,
    inventory: Offset
  ): Offset {
    Monster.startMonster(builder);
    Monster.addPos(builder, pos);
    Monster.addMana(builder, mana);
    Monster.addHp(builder, hp);
    Monster.addName(builder, name);
    Monster.addInventory(builder, inventory);
    return Monster.endMonster(builder);
  }
}

Node.js Usage

FlatBuffers works seamlessly in Node.js environments with Buffer integration.

// Node.js specific usage patterns:

import { Builder, ByteBuffer } from 'flatbuffers';
import * as fs from 'fs';

// Reading from file system
const buffer = fs.readFileSync('data.bin');
const uint8Buffer = new Uint8Array(buffer);
const bb = new ByteBuffer(uint8Buffer);

// Writing to file system  
const builder = new Builder(1024);
// ... build data ...
const outputBuffer = builder.asUint8Array();
fs.writeFileSync('output.bin', Buffer.from(outputBuffer));

// Network usage with HTTP
import * as http from 'http';

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    const chunks: Buffer[] = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => {
      const buffer = Buffer.concat(chunks);
      const bb = new ByteBuffer(new Uint8Array(buffer));
      // Process FlatBuffer data...
    });
  }
});

Browser Usage

FlatBuffers integrates well with modern browser APIs and frameworks.

// Browser-specific usage patterns:

// Fetch API integration
async function loadFlatBuffer(url: string): Promise<ByteBuffer> {
  const response = await fetch(url);
  const arrayBuffer = await response.arrayBuffer();
  return new ByteBuffer(new Uint8Array(arrayBuffer));
}

// Web Workers
// main.js
const worker = new Worker('flatbuffer-worker.js');
const builder = new Builder(1024);
// ... build data ...
const buffer = builder.asUint8Array();
worker.postMessage({ buffer }, [buffer.buffer]);

// flatbuffer-worker.js
self.onmessage = function(e) {
  const bb = new ByteBuffer(new Uint8Array(e.data.buffer));
  // Process data...
};

// IndexedDB storage
function storeFlatBuffer(db: IDBDatabase, data: Builder): Promise<void> {
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['flatbuffers'], 'readwrite');
    const store = transaction.objectStore('flatbuffers');
    const buffer = data.asUint8Array();
    const request = store.put({ id: 1, data: buffer });
    request.onsuccess = () => resolve();
    request.onerror = () => reject(request.error);
  });
}

Complete Usage Example:

import { Builder, ByteBuffer } from 'flatbuffers';
import { Monster, Vec3 } from './generated/monster';

// Create a monster
const builder = new Builder(1024);

// Create components
const pos = new Vec3(1.0, 2.0, 3.0);
const posOffset = Vec3.pack(builder, pos);
const nameOffset = builder.createString("Dragon");
const inventoryData = [1, 2, 3, 4, 5];
const inventoryOffset = Monster.createInventoryVector(builder, inventoryData);

// Build monster
const monster = Monster.createMonster(
  builder,
  posOffset,
  200,    // mana
  150,    // hp  
  nameOffset,
  inventoryOffset
);

// Finish and get buffer
builder.finish(monster);
const buffer = builder.asUint8Array();

// Read the data back
const bb = new ByteBuffer(buffer);
const readMonster = Monster.getRootAsMonster(bb);

console.log(`Name: ${readMonster.name()}`);
console.log(`HP: ${readMonster.hp()}`);
console.log(`Mana: ${readMonster.mana()}`);

const position = readMonster.pos();
if (position) {
  console.log(`Position: ${position.x}, ${position.y}, ${position.z}`);
}

console.log(`Inventory size: ${readMonster.inventoryLength()}`);
for (let i = 0; i < readMonster.inventoryLength(); i++) {
  console.log(`Item ${i}: ${readMonster.inventory(i)}`);
}

docs

cpp-core.md

flexbuffers.md

go.md

index.md

java.md

javascript.md

python.md

rust.md

schema-compiler.md

verification.md

tile.json