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.
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:
Runs real capnstruct binary views in your current JS engine against native JSON.parse / JSON.stringify.
for (const row of view) (0 heap allocs) vs JSON.parse + looprow.callsign = 'UPD' vs JSON round-tripview.column('val') for Arrow / WebGPU / SIMD vs JS array map
Mutate strings & slices in place, push/pop rows, and watch the fixed #[repr(C)] table and tail arena update in real time.
.length): 0.bytes.byteLength): 0 B.compactionRatio): 0.0%for (const row of users)) — Edit fields inline:#[repr(C)] Codegen
Select a preset schema to inspect its exact C-ABI alignment, field offsets, and generated Rust rustc compile-time layout assertions.
toRustReprC([...]) Output:For years, JavaScript and TypeScript developers had to choose between two painful extremes:
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.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 arrayconst 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 decodingfor (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 / SIMDconst 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.