TypeScript
ZAP TypeScript SDK - zero-dependency zero-copy wire runtime + zapgen codegen
TypeScript Binding
@zap-proto/zap is the native ZAP wire
runtime for TypeScript. It has zero runtime dependencies and implements the
ZAP wire format directly, so it is byte-compatible with the Go runtime
(github.com/zap-proto/go) — a buffer built
in TypeScript parses through Go unchanged, and vice versa.
The root entry is universal: it imports no Node built-ins and bundles cleanly
into a browser. The Node-only TCP transport lives behind the /node sub-path so
node:net never leaks into a browser bundle.
Installation
npm install @zap-proto/zap
# or
pnpm add @zap-proto/zap
# or
yarn add @zap-proto/zapEntry points
| Sub-path | Role |
|---|---|
@zap-proto/zap | wire / view / builder / envelope — universal, no Node built-ins. |
@zap-proto/zap/node | ZapClient (TCP, node:net) + two-connection promise pipeline. |
// Universal (browser + Node): wire codec, views, builders, the call envelope.
import { Builder, Message, buildRequest, parseResponse } from '@zap-proto/zap';
// Node only: TCP RPC client + promise pipelining.
import { ZapClient, pipeline } from '@zap-proto/zap/node';Round-trip
Build a message, then parse it back zero-copy. The Builder lays out a struct's
fixed section; Message.parse validates the header and root() returns a view
that reads fields straight out of the buffer — no decode step.
import { Builder, Message } from '@zap-proto/zap';
// Write: a struct with one Float32 field at offset 0 and one at offset 4.
const builder = new Builder();
const point = builder.startObject(8); // 8-byte fixed section
point.setF32(0, 1.5);
point.setF32(4, -2.5);
point.finishAsRoot();
const bytes = builder.finish(); // Uint8Array — the ZAP wire message
// Read: zero-copy. The Message aliases `bytes`; nothing is deserialized.
const root = Message.parse(bytes).root(); // StructView over the same bufferField getters on StructView are protected — you read fields through a typed
subclass, which zapgen generates per struct from a .zap schema (see
Codegen). The hand-written builder above is the runtime that
generated builders compile to; in real code you call newPoint(...) /
PointView instead of raw offsets.
Codegen
@zap-proto/zap ships the zapgen CLI as a bin, so any consumer can generate
TypeScript bindings from a .zap schema after install:
npx zapgen schema.zap # writes schema_zap.ts next to the input
npx zapgen -out ./src/gen schema.zap # writes into the given directory
npx zapgen --emit=openapi schema.zap # writes schema.openapi.json (OpenAPI 3.1)
npx zapgen --emit=ts,openapi schema.zap # writes both targetsFor an Echo service schema, zapgen emits echo_zap.ts containing one
StructView subclass + builder per struct, an EchoMethod ordinal table, an
EchoClient with one async method per declared method, and an abstract
EchoServer whose dispatch(envelope) decodes the ordinal and routes to the
matching handler. The generated file imports from @zap-proto/zap and is
byte-compatible with the Go runtime over the wire.
Method ordinals are auto-assigned 1, 2, 3, … in declaration order, so appending
a method never renumbers existing ones — wire compatibility is preserved.
Call envelope
The envelope module carries an RPC call over the wire: a message type, a method
ordinal, and an optional capability target. Generated clients/servers use it; you
can also drive it directly.
import { buildRequest, parseResponse, NO_TARGET, Status, Message } from '@zap-proto/zap';
// Build a request envelope around a payload (a built struct's bytes).
const reqBytes = buildRequest({
method: 1, // the .zap interface method ordinal
promiseID: 0,
target: NO_TARGET, // not pipelining off an earlier promise
cap: new Uint8Array(0),
payload, // e.g. the Uint8Array from builder.finish()
});
// On the response side:
const res = parseResponse(responseBytes);
if (res.status === Status.OK) {
const result = Message.parse(res.body).root(); // the response struct, zero-copy
}Node TCP client
The /node sub-path speaks the ZAP TCP framing (length-prefix + nodeID
handshake + correlated request/response frames):
import { ZapClient } from '@zap-proto/zap/node';
import { EchoClient } from './gen/echo_zap.js';
const conn = await ZapClient.connect('localhost:9000');
const echo = new EchoClient(conn);
const resp = await echo.echo({ msg: 'hello' });
console.log(resp.msg);
conn.close();Develop
pnpm install # runs prepare → tsup → dist/
pnpm build # tsup --format esm --dts
pnpm test # vitest — byte-identical Go fixture + round-trips
pnpm typecheck # tsc --noEmit (strict)Examples
Full examples at:
- test/fixtures/echo.zap — the schema the codegen tests drive
- test/wire.test.ts — round-trips against a Go-produced fixture