Files
6f0dfd20c5 docs: complete tooling story (pb2zap + zap2pb), zip server, ZAP-only production case (#1)
* docs: tools section (pb2zap, zap2pb), zip server page, ZAP-only production story

- content/docs/tools/: new Tooling nav group — the complete pb<->ZAP
  interop boundary. pb2zap (go/ast codemod off protobuf), zap2pb
  (the one sanctioned protobuf import: Marshal/Unmarshal/Equal/
  AppendMessage, v0.2.0), and the migration path over the existing
  schema/codegen pages.
- content/docs/zip.mdx: the ZAP-native application server — one
  Listen verb (ZAP primary, HTTP secondary), typed handlers with
  REST/OpenAPI/MCP projections, Mount composition, and the
  hanzoai/cloud case: sub-1,000-line compose root hosting ~75K LOC
  of subsystems (76:1).
- why-zap: scrub the unmeasured benchmark tables (per the
  benchmarks-page policy), replace with mechanism arguments and the
  ZAP-only-in-production account: zero gRPC, protobuf confined to
  the four-function zap2pb boundary at the OTLP edge.
- ecosystem: add zip/pb2zap/zap2pb rows; quick-start + index: link
  the new pages, drop the unmeasured 5-20x claim.

* fix(build): alias MDX collections to regenerated docs/, drop frozen .docs snapshot

next.config.mjs and tsconfig.json pointed @hanzo/docs-mdx:collections/*
at .docs/ — a snapshot frozen on Jun 1 — while the MDX plugin
regenerates docs/ on every build. Result: no page added since the
snapshot (schema, codegen, rpc, capabilities, and this PR%27s tools/zip)
was ever emitted; the site silently served 37 routes instead of 45.
Alias both resolvers at docs/ and remove .docs.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-07 17:58:42 -07:00

328 lines
9.4 KiB
Plaintext

---
title: Quick Start
description: Get started with ZAP Protocol in 5 minutes
---
# Quick Start
This guide builds your first ZAP service end-to-end: write a schema, generate
typed code, implement a server, and call it from a client.
## Prerequisites
- Go 1.21+ or Rust 1.75+ or Node.js 20+
- The ZAP schema compiler (installed below)
## Installation
<Tabs items={["Go", "Rust", "TypeScript"]}>
<Tab value="Go">
```bash
# Add the runtime
go get github.com/zap-proto/[email protected]
# Install the schema compiler (the zapgen binary)
go install github.com/zap-proto/go/cmd/[email protected]
```
</Tab>
<Tab value="Rust">
```bash
# Install the schema compiler (provides the zapc binary)
cargo install zap-schema
# Add the runtime crates to Cargo.toml
# zap = { git = "https://github.com/zap-proto/rust" }
# zapc = { git = "https://github.com/zap-proto/rust" }
# zap-rpc = { git = "https://github.com/zap-proto/rust" }
```
</Tab>
<Tab value="TypeScript">
```bash
# The runtime ships the zapgen CLI as a bin
npm install @zap-proto/zap
# Or with pnpm
pnpm add @zap-proto/zap
```
</Tab>
</Tabs>
## Define Your Schema
Create a file named `calculator.zap`. ZAP is whitespace-significant — no braces,
no file ID, and ordinals are assigned automatically. The legacy Cap'n Proto brace
form still parses; the two produce byte-identical schemas.
<SchemaTabs
filename="calculator.zap"
zap={`# ZAP schema - no file ID needed, no ordinals
struct Operands
A Float64
B Float64
struct Value
Result Float64
interface Calculator
# Methods get implicit ordinals 1, 2, 3, … in declaration order
add (req Operands) -> (resp Value)
subtract (req Operands) -> (resp Value)
multiply (req Operands) -> (resp Value)
divide (req Operands) -> (resp Value)`}
capnp={`@0x85150b117366d14b; # file ID required
struct Operands {
A @0 :Float64;
B @1 :Float64;
}
struct Value {
Result @0 :Float64;
}
interface Calculator {
add @0 (req :Operands) -> (resp :Value);
subtract @1 (req :Operands) -> (resp :Value);
multiply @2 (req :Operands) -> (resp :Value);
divide @3 (req :Operands) -> (resp :Value);
}`}
/>
## Generate Code
<Tabs items={["Go", "Rust", "TypeScript"]}>
<Tab value="Go">
```bash
# Emit <struct>_zap.go files next to the schema
zapgen calculator.zap
# Or into a directory
zapgen -out ./gen calculator.zap
```
</Tab>
<Tab value="Rust">
```bash
# One-shot generation with the zap-schema compiler
zapc generate calculator.zap --lang rust --out ./gen/
# Or wire it into a cargo build from build.rs:
# zapc::CompilerCommand::new().file("calculator.zap").run().unwrap();
```
</Tab>
<Tab value="TypeScript">
```bash
# zapgen ships in @zap-proto/zap; writes calculator_zap.ts
npx zapgen calculator.zap
# Or into a directory
npx zapgen -out ./src/gen calculator.zap
```
</Tab>
</Tabs>
Generation emits a zero-copy View + Builder per struct, plus — for the
`interface` — a typed client and an abstract server over the ZAP call envelope.
See [Code Generation](/docs/codegen) for the full backend matrix.
> Go's self-contained `zapgen` reads the wire-type spelling for scalars
> (`f64`, `u64`, `f32`, `u32`, …) and pins offsets explicitly, e.g.
> `A f64 @0`. The canonical front-end the other backends drive uses the
> capitalized names above (`Float64`, `UInt64`) with auto-assigned offsets;
> both describe the same wire layout.
## Implement the Server
The generated `Calculator` server is an abstract handler: implement each method,
and the generated dispatcher decodes the call envelope and routes by method
ordinal.
<Tabs items={["Go", "Rust", "TypeScript"]}>
<Tab value="Go">
```go
package main
import (
"errors"
"github.com/zap-proto/go/rpc"
"yourmodule/gen"
)
type calculator struct{}
func (calculator) Add(req []byte) ([]byte, error) {
op, err := gen.WrapOperands(req)
if err != nil {
return nil, err
}
return gen.NewValue(gen.ValueInput{Result: op.A() + op.B()}), nil
}
func (calculator) Divide(req []byte) ([]byte, error) {
op, err := gen.WrapOperands(req)
if err != nil {
return nil, err
}
if op.B() == 0 {
return nil, errors.New("division by zero")
}
return gen.NewValue(gen.ValueInput{Result: op.A() / op.B()}), nil
}
// Subtract and Multiply follow the same shape.
// In your transport's read loop, dispatch each inbound envelope:
// respBytes, err := gen.DispatchCalculator(calculator{}, envelope)
// then write respBytes back. The rpc package defines the envelope (the bytes);
// the transport that carries it is yours.
var _ = rpc.StatusOK
```
</Tab>
<Tab value="Rust">
```rust
use zap_rpc::new_client;
use crate::calculator_zap::calculator;
struct CalculatorImpl;
impl calculator::Server for CalculatorImpl {
async fn add(
self: std::rc::Rc<Self>,
params: calculator::AddParams,
mut results: calculator::AddResults,
) -> Result<(), ::zap::Error> {
let req = params.get()?.get_req()?;
results.get().init_resp().set_result(req.get_a() + req.get_b());
Ok(())
}
async fn divide(
self: std::rc::Rc<Self>,
params: calculator::DivideParams,
mut results: calculator::DivideResults,
) -> Result<(), ::zap::Error> {
let req = params.get()?.get_req()?;
if req.get_b() == 0.0 {
return Err(::zap::Error::failed("division by zero".into()));
}
results.get().init_resp().set_result(req.get_a() / req.get_b());
Ok(())
}
// subtract and multiply follow the same shape.
}
// Host it as the bootstrap capability:
// let client: calculator::Client = new_client(CalculatorImpl);
// then serve `client` over a TwoParty VatNetwork (see the Rust SDK guide).
```
</Tab>
<Tab value="TypeScript">
```typescript
import { CalculatorServer, Operands, newValue } from './gen/calculator_zap.js';
// Methods take and return raw ZAP bytes; wrap/build at the edges.
class Calculator extends CalculatorServer {
async add(req: Uint8Array): Promise<Uint8Array> {
const op = Operands.wrap(req);
return newValue({ result: op.a + op.b }); // getters are property accessors
}
async divide(req: Uint8Array): Promise<Uint8Array> {
const op = Operands.wrap(req);
if (op.b === 0) throw new Error('division by zero');
return newValue({ result: op.a / op.b });
}
// subtract and multiply follow the same shape.
}
// CalculatorServer.dispatch(envelope) decodes the ordinal and routes to the
// matching handler; drive it from your transport's read loop.
```
</Tab>
</Tabs>
## Create a Client
The generated client carries one typed method per declaration; each builds a
request envelope, ships it over the channel you supply, and returns the decoded
response.
<Tabs items={["Go", "Rust", "TypeScript"]}>
<Tab value="Go">
```go
package main
import (
"fmt"
"yourmodule/gen"
)
func main() {
// channel is your EchoChannel/CalculatorChannel — the read/write loop that
// ships a request envelope and awaits its correlated rpc.Response.
calc := gen.NewCalculatorClient(channel, nil) // nil = no capability token
// A request method returns (rpc.Promise, body, error); the promise enables
// pipelining a dependent call before this one returns.
_, body, err := calc.Add(gen.NewOperands(gen.OperandsInput{A: 10, B: 5}))
if err != nil {
panic(err)
}
v, _ := gen.WrapValue(body)
fmt.Printf("10 + 5 = %.0f\n", v.Result()) // 15
}
```
</Tab>
<Tab value="Rust">
```rust
use crate::calculator_zap::calculator;
use zap_rpc::{rpc_twoparty_zap, RpcSystem};
// `calc` is the bootstrapped remote interface (see the Rust SDK guide for
// constructing the RpcSystem over a connection).
let calc: calculator::Client = rpc_system.bootstrap(rpc_twoparty_zap::Side::Server);
let mut request = calc.add_request();
{
let mut req = request.get().init_req();
req.set_a(10.0);
req.set_b(5.0);
}
let reply = request.send().promise.await?;
println!("10 + 5 = {}", reply.get()?.get_resp()?.get_result()); // 15
```
</Tab>
<Tab value="TypeScript">
```typescript
import { ZapClient } from '@zap-proto/zap/node';
import { CalculatorClient, newOperands, Value } from './gen/calculator_zap.js';
const conn = await ZapClient.connect('localhost:9000');
const calc = new CalculatorClient(conn);
// Methods take and return raw ZAP bytes; build the request, wrap the response.
const body = await calc.add(newOperands({ a: 10, b: 5 }));
console.log(`10 + 5 = ${Value.wrap(body).result}`); // 15
conn.close();
```
</Tab>
</Tabs>
## Next Steps
Now that you have a working ZAP service:
- Pick a language-specific SDK guide: [TypeScript](/docs/sdks/typescript), [Go](/docs/sdks/go), [Python](/docs/sdks/python), [Rust](/docs/sdks/rust), [C++](/docs/sdks/cpp), [Java](/docs/sdks/java), [C#](/docs/sdks/csharp), [Erlang](/docs/sdks/erlang), [Haskell](/docs/sdks/haskell), [OCaml](/docs/sdks/ocaml), or [C](/docs/sdks/c)
- Learn the [Schema Language](/docs/schema) and [Code Generation](/docs/codegen) in depth
- Understand the [RPC System](/docs/rpc) — the call envelope, generated stubs, and promise pipelining
- Read the [Architecture](/docs/architecture) and explore [Transports](/docs/transports)
- Serve it with [zip](/docs/zip) — the ZAP-native application server
- Carry a higher-level protocol on ZAP: [HTTP](/docs/protocols/http), [MCP](/docs/protocols/mcp), [A2A](/docs/protocols/a2a), [ACP](/docs/protocols/acp), [RNS](/docs/protocols/rns), [FIX](/docs/protocols/fix), [WS](/docs/protocols/ws)
- Migrating off protobuf? The [tools section](/docs/tools) covers `pb2zap` and `zap2pb`
- See [benchmarks](/docs/benchmarks) — measured, reproducible numbers from the bench harness