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.
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';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();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[];
}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 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()); // 3When 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);
}
}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...
});
}
});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)}`);
}