Skip to content

With Durable Objects

Each Durable Object has its own WASM instance with its own WebAssembly.Memory. zerobuf wraps that memory, giving you structured zero-copy access to WASM data.

import { DurableObject } from "cloudflare:workers";
import { zerobuf, type ZeroBuf } from "zerobuf";
export class FragmentDO extends DurableObject<Env> {
private buf!: ZeroBuf;
private wasmExports!: WasmExports;
private async ensureInitialized() {
if (this.buf) return;
const instance = await WebAssembly.instantiate(wasmModule);
this.wasmExports = instance.exports as WasmExports;
this.buf = zerobuf(
this.wasmExports.memory,
this.wasmExports.__heap_base.value,
);
}
// Public method = RPC endpoint
async scan(request: ScanRequest): Promise<ScanResult> {
await this.ensureInitialized();
// Save arena state before per-request allocations
const checkpoint = this.buf.save();
// WASM computes, writes result at a known pointer
this.wasmExports.executeScan(request.ptr);
const resultPtr = this.wasmExports.getResultPtr();
// Wrap as accessor object — lazy, zero copy
const result = this.buf.wrapObject(resultPtr);
// Convert to plain JS before returning over RPC
// (RPC serializes, so we need a plain JS object)
const response = result.toJS() as ScanResult;
// Restore arena — frees all per-request allocations
this.buf.restore(checkpoint);
return response;
}
}

One zerobuf per DO instance. Each DO has its own WASM memory. Create the zerobuf instance once in ensureInitialized().

toJS before RPC. zerobuf Proxy objects reference WASM memory via absolute pointers. When you return data over DO RPC, call .toJS() to convert into a plain JS object that can be serialized.

Arena lifecycle. WASM memory persists as long as the DO is alive (until hibernation evicts it). Use buf.save()/buf.restore() for per-request cleanup — save before each request, extract results with .toJS(), then restore to free all per-request allocations. Without this, the arena leaks memory on every request.

No race conditions. JS and WASM on the same DO never run concurrently — each RPC call runs to completion before the next one starts.

Data typeUse
Query results, metadata, configszerobuf — structured, lazy access
Typed columns (Float64Array, Int64)Raw typed buffers — tighter packing, SIMD-friendly
Strings, nested objectszerobuf — handles encoding, growth
Hot compute pathsRaw buffers for input, zerobuf for output