mirror of
https://github.com/zap-proto/dev.git
synced 2026-10-30 15:05:18 +00:00
* 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]>
328 lines
9.4 KiB
Plaintext
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
|