ZAP Protocol
SDKs

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/zap

Entry points

Sub-pathRole
@zap-proto/zapwire / view / builder / envelope — universal, no Node built-ins.
@zap-proto/zap/nodeZapClient (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 buffer

Field 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 targets

For 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:

On this page