Skip to content

capnstruct

The new standard for structured data in JavaScript & TypeScript. Mutate strings, push/pop rows, extract zero-copy Arrow/SIMD columns, and share
< 4.5 KiB Minified + Gzipped (0 deps)~27x–49x Faster than JSON.parse~767x Faster In-Place String MutationExact C/Rust #[repr(C)] ABI + Tail ArenaZero-Copy TypedArray Columns for Arrow / WebGPU

Run the real capnstruct engine in your browser right now—benchmark zero-copy iteration against JSON.parse, mutate variable-length strings and slices in-place while inspecting raw wire bytes, and generate rustc-verified #[repr(C)] structs:

Zero-Copy vs JSON.parse / JSON.stringify in Your Browser

Runs real capnstruct binary views in your current JS engine against native JSON.parse / JSON.stringify.

1. 100,000 Numeric Struct Scan (2.4 MiB)
Flyweight cursor for (const row of view) (0 heap allocs) vs JSON.parse + loop
JSON.parse + loop
-- ms
capnstruct for..of
-- ms
Click "Run Live Benchmark"
2. 25,000 Flight Telemetry Rows (13 Fields)
Sparse BBox filter (~0.4% hit rate) + lazy UTF-8 string decode only on matched rows
JSON.parse + filter
-- ms
capnstruct zero-copy
-- ms
Click "Run Live Benchmark"
3. Live In-Place String Mutation (25,000 Rows)
Mutate 100 callsigns in-place via row.callsign = 'UPD' vs JSON round-trip
JSON parse+stringify
-- ms
capnstruct in-place
-- ms
Click "Run Live Benchmark"
4. Columnar Float64Array Extraction (100,000 Rows)
Zero-copy view.column('val') for Arrow / WebGPU / SIMD vs JS array map
JSON array .map()
-- ms
capnstruct .column()
-- ms
Click "Run Live Benchmark"
Interactive Binary StructArrayView Inspector

Mutate strings & slices in place, push/pop rows, and watch the fixed #[repr(C)] table and tail arena update in real time.

Rows (.length): 0
Struct Stride: 24 B
Wire Bytes (.bytes.byteLength): 0 B
Fragmentation (.compactionRatio): 0.0%
Last Action: Initialized
Live JS View (for (const row of users)) — Edit fields inline:
Raw Wire Buffer Hex + ASCII Inspector (Fixed Table + Tail Arena):
 
Live TypeScript Schema → Rust #[repr(C)] Codegen

Select a preset schema to inspect its exact C-ABI alignment, field offsets, and generated Rust rustc compile-time layout assertions.

TypeScript Schema & Computed Offsets:
 
Generated Rust toRustReprC([...]) Output:
 

For years, JavaScript and TypeScript developers had to choose between two painful extremes:

  1. Plain JS Objects & JSON.parse / JSON.stringify: Ergonomic (row.lat, row.callsign = 'NEW', rows.push(...), { ...row }), but parses every byte upfront, allocates millions of short-lived V8 heap objects, triggers GC pauses, and requires full re-serialization to cross Worker or WASM boundaries.
  2. Manual DataView / apache-arrow / Protobuf: Zero-copy or binary-compact, but breaks normal JS ergonomics (dv.getFloat64(base + i * 24 + 8, true) or table.getChild('lat').get(i)), produces immutable buffers where you cannot assign row.name = 'new' or rows.push(...), and ships 100–150+ KiB of runtime code.

capnstruct gives you both at once in a 0-dependency, < 4.5 KiB runtime:

import { defineStruct, u32, f64, str, slice } from 'capnstruct';
const Telemetry = defineStruct('Telemetry', {
id: u32,
lat: f64,
lon: f64,
callsign: str,
tags: slice(u32),
});
// 1. Growable binary array — push & pop like a normal JS array
const rows = Telemetry.alloc(0);
rows.push(
{ id: 1, lat: 37.6188, lon: -122.375, callsign: 'SFO_101', tags: [10, 20] },
{ id: 2, lat: 51.47, lon: -0.4543, callsign: 'LHR_202', tags: [30] },
);
// 2. Zero-allocation flyweight iteration + lazy UTF-8 decoding
for (const row of rows) {
if (row.lat > 40) {
// 3. Live in-place mutation of primitives, strings, and slices!
row.callsign = 'LHR_PRIORITY';
}
}
// 4. Columnar TypedArray projection for Apache Arrow / WebGPU / SIMD
const lats: Float64Array = rows.column('lat');
// 5. Wire-ready Uint8Array (automatically compacts dead tail bytes if needed)
const wireBytes: Uint8Array = rows.bytes;

Compiled V8 Monomorphic Accessors

Each defineStruct() compiles a specialized view class with pre-allocated V8 fast-property slots and zero Proxy traps. TurboFan inlines field reads directly into DataView loads.

Mutable Strings, Slices & .push()/.pop()

Assign row.callsign = 'NEW' or row.tags = [1, 2, 3] directly on binary views. Shorter/equal values overwrite in-place in $O(1)$ with zero allocations; longer values bump-allocate in the tail arena.

Columnar Arrow / WebGPU Projections

Call rows.column('lat') to extract a contiguous Float64Array / Int32Array (zero-copy when stride equals field size) or bulk-write columns via rows.setColumn('lat', values).

Exact Rust #[repr(C)] & WasmArena

Matches C/Rust #[repr(C)] layout byte-for-byte with 8-byte [u32 relOffset, u32 len] tail descriptors. Auto-rebinds views when WebAssembly.Memory.grow() detaches buffers, and emits rustc-verified Rust structs.