Memory efficient cross-platform serialization library with zero-copy deserialization supporting 15+ programming languages.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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)}`);
}