zip is shared protocol infra (the fiber-over-ZAP web framework used by every
Lux/Hanzo/Zoo Go binary — nodes, gateway, cloud, iam, kms, o11y, ai, …), so its
home is the ZAP protocol family (zap-proto/{go,http,zapd,zip}), NOT any one org's
brand namespace. This consolidates the rich framework (formerly hanzoai/zip v0.5.0
— Ctx/Handler/Errorf/Err*/HTTPError, typed handlers, middleware, runtime/extension
loading, openapi, validate, zaprpc) with the real ZAP transport (ListenZAP/
ListenHTTP/Serve over zap-proto/http) into ONE canonical module.
Module path github.com/hanzoai/zip -> github.com/zap-proto/zip. Code identical to
hanzoai/zip v0.5.0; promoted to v1.0.0 as the stable shared-infra framework. The
prior hanzoai/zip lineage is retired — one home, one import path, one way.
Verified: go build/vet/test ./... green; the ZAP round-trip integration test serves
/v1/health over the real ZAP transport (200 + handler JSON).
zip
Hanzo's canonical Go web framework. Built on Fiber v3 / fasthttp. Sinatra-style API. ZAP-typed handlers. Multi-language extension support via HIP-0105.
ONE framework. ZERO escape hatches. zip IS fast.
package main
import (
"github.com/hanzoai/zip"
"github.com/hanzoai/zip/middleware"
)
func main() {
app := zip.New(zip.Config{})
app.Use(middleware.Recover(), middleware.RequestID())
app.Get("/health", func(c *zip.Ctx) error {
return c.JSON(200, map[string]string{"status": "ok"})
})
v1 := app.Group("/v1")
v1.Get("/users/:id", func(c *zip.Ctx) error {
return c.JSON(200, map[string]string{
"id": c.Param("id"),
"org": c.Org(), // HIP-0026 gateway-minted X-Org-Id
"user": c.User(), // X-User-Id
})
})
_ = app.Serve(":9653", ":8080") // ZAP primary + HTTP extra
}
Features
- Sinatra/Express idiom —
app.Get(path, fn)is the primary API. - Typed handlers —
zip.Get[In, Out](app, path, fn)generates OpenAPI 3.1 spec and serves Swagger UI at/docs. - Hanzo identity built-in —
c.Org() / c.User() / c.UserEmail()pull JWT-validated values from gateway X-* headers per HIP-0026. - luxfi/log for all logging — no
slogorzapin zip. - Extension routes —
app.Module("POST /v1/eval", "wasm", "./policy")mounts any HIP-0105 extension as a route. Supports wasm (wazero) / goja / pyvm / starlark / v8go / native. - Embedded JS runtime —
runtime.NewJSRuntimeruns TS/JS handlers in-process via goja (pure Go, no CGO).runtime.TranspileToES5compiles TS → goja-ready JS via esbuild.runtime.JSHandler/runtime.JSModulemount an Express-shaped JS function as a route. - WebSocket —
wsx.Upgrade(fn)via fasthttp/websocket. - SSE / streaming —
c.SendStreamWriter(Fiber v3 native). - Drop-in migration —
app.Mount("/legacy", chiRouter)for anyhttp.Handler(chi, gin, beego, net/http). - ZAP transport —
app.ListenZAP(":9653")serves the whole app over ZAP (TLS 1.3 + post-quantum, gRPC's replacement);app.ListenHTTP(":8080")is the optional plain-HTTP extra;app.Serve(zapAddr, httpAddr)runs both. Your routes ARE the ZAP surface — same handlers, middleware, and auth over either transport, no separate RPC registration. - Named-service RPC (optional) —
zaprpc.Registry+zaprpc.HTTPHandler(reg)exposes generatedzapcservices by name at a route, for a gRPC-style service surface on top of the transport.
Install
go get github.com/hanzoai/zip
Module path: github.com/hanzoai/zip. Go version: 1.26.3 (forced by
luxfi/log).
JSON: encoding/json/v2 at the edge
zip routes every JSON path — c.JSON, c.Bind().Body, the typed
zip.Get[In,Out] round-trip, the HIP-0105 module envelope, the
auto-OpenAPI spec — through one internal helper
(internal/jsonenc). When the binary is compiled with
GOEXPERIMENT=jsonv2 (Go 1.25+), that helper is backed by the stdlib
encoding/json/v2; otherwise it falls back to encoding/json v1.
There is no third-party JSON library: stdlib only, per HIP-0106's
canonical Hanzo Go stack.
# Compile with v2 (preferred — ~10% faster on the edge,
# ~25% fewer allocations per request)
GOEXPERIMENT=jsonv2 go build ./...
# Without the experiment, v1 is selected:
go build ./...
zip.JSONVariant is a build-time constant exposing which impl is
active. zip.New logs it once at startup so operators can confirm v2
is on in CI/prod logs:
{"level":"info","module":"zip","json_variant":"encoding/json/v2","message":"zip new"}
Benchmarks (Apple M1 Max, Go 1.26, fiber/v3):
| Bench | json v1 ns/op | json/v2 ns/op | v1 allocs | v2 allocs | Δ |
|---|---|---|---|---|---|
| Edge POST + JSON roundtrip | 14972 | 13631 | 73 | 56 | -9% time, -23% allocs |
| Marshal-only | 10798 | 7924 | 34 | 34 | -27% time |
| Unmarshal-only | 13803 | 12729 | 67 | 50 | -8% time, -25% allocs |
Reproduce with go test -bench=BenchmarkJSON -benchmem ./... and
again with GOEXPERIMENT=jsonv2.
Per HIP-0106 "Wire protocol stack": JSON marshalling happens at most
ONCE per request (at the subsystem handler boundary, through zip).
Inter-subsystem calls use ZAP-typed Go values via cloud.Deps. JSON
is the edge format only.
Embedded JS runtime — the TS migration path
zip embeds a JavaScript runtime so legacy TS/JS handlers run in the
same Go process — no separate hanzo/runtime service, no
inter-service RPC, no container-per-service. Combined with the
single-binary architecture below, this is where the cloud savings come
from: one process mounts everything.
TS source --esbuild target=ES2015--> ES JS --drop into--> embedded goja --in-process--> Fiber route
// 1. Transpile legacy TS to goja-runnable JS (pure Go, no CGO).
js, _ := runtime.TranspileToES5(tsSource, runtime.ESOptions{Loader: "ts"})
// 2. Pool-backed embedded VM; register the CommonJS module.
rt, _ := runtime.NewJSRuntime(runtime.JSOptions{PoolSize: 8})
_ = rt.LoadModule("app", string(js)) // module.exports = (req, res) => ...
// 3. Express-shaped handler -> fiber.Handler, mounted on zip.
h, _ := runtime.JSModule(rt, "app")
app.Fiber().All("/legacy/*", h)
The JS handler sees an Express-shaped (req, res) pair —
req.method / req.path / req.query / req.headers / req.body and
res.status(n) / res.set(k,v) / res.json(v) / res.send(v). JSON
encode/decode routes through the same internal/jsonenc impl as the
edge, so there is one wire format.
Migrate in place, incrementally: start with the TS handler running
in goja (zero rewrite), then rewrite hot routes to native Go
func(c *zip.Ctx) error one at a time. Both styles coexist on the same
App.
esbuild's pure-Go API emits ES2015 as its lowest target (it does not emit literal ES5); goja runs ES2015 output.
TranspileToES5is named for the migration intent — "down to what the embedded VM runs."
A pool of *goja.Runtime (lifted from hanzoai/base/plugins/gojavm)
keeps VMs hot so requests don't pay per-request VM-creation cost;
goja VMs are single-threaded, so each request borrows an isolated VM.
See examples/express-in-zip/ for the full esbuild → goja → Fiber
proof point with an integration test.
Single-binary architecture (HIP-0106)
zip is the seam that lets hanzoai/cloud mount every Hanzo subsystem
into ONE Go process. Each subsystem exposes Mount(app *zip.App, deps)
(see examples/subsystem-mount); cloud builds the dependency bag once
and threads it through every mount. Native Go subsystems, embedded-JS
legacy handlers, WebSocket endpoints, and the ZAP RPC plane all run on
the same App. No microservice overhead, no inter-service RPC, no
container-per-service — JSON only at the edge, ZAP-typed Go values
between subsystems.
Architecture
zip.Appwraps*fiber.App. One binary, one server, no escape hatches — no.Fastvariant API, no second router.zip.Ctxwrapsfiber.Ctxand adds Hanzo identity sugar (c.Org() / c.User() / c.IsAdmin() / c.RequestID() / c.Log()).- Handlers are
func(c *zip.Ctx) error. Returning a*zip.HTTPErrorcontrols the response status; everything else becomes 500 JSON. - Middleware lives in
zip/middleware/—Recover,Logger,RequestID,RateLimit,CORS,MaxBody,Telemetry. Auth- specific middleware (Auth,StripIdentityHeaders) lives ingithub.com/hanzoai/gateway/middlewareper HIP-0106. - Adapters in
zip/adapt.go—AdaptNetHTTP / AdaptNetHTTPFunc / AdaptNetHTTPMiddleware. Migration tools only — replace adapted routes with native handlers when feasible. - Extension runtime contract in
zip/runtime/— duck-typedruntime.Loaderinterface; zip does NOT pullhanzoai/baseas a dep. Service binaries construct*extruntime.Loaderand inject viazip.Config.Loader.
Examples
| Example | Demonstrates |
|---|---|
examples/hello |
Minimal Sinatra-style API |
examples/zap-typed |
Generic typed handler + auto-OpenAPI |
examples/express-in-zip |
Legacy TS handler: esbuild → goja → Fiber |
examples/subsystem-mount |
HIP-0106 Mount(*App, deps) idiom |
examples/module-routes |
app.Module() over a runtime.Loader |
examples/websocket |
wsx.Upgrade echo server |
examples/sse-streaming |
Server-Sent Events via SendStreamWriter |
examples/migrate-from-gin |
gin→zip mechanical port |
examples/migrate-from-chi |
chi→zip via AdaptNetHTTP |
examples/migrate-from-beego |
beego→zip via AdaptNetHTTP |
Migration
| From | Adapter |
|---|---|
| net/http | zip.AdaptNetHTTP(httpHandler) |
| chi | zip.AdaptNetHTTP(chiRouter) (chi.Router IS http.Handler) |
| gin | zip.AdaptNetHTTP(ginEngine) (gin.Engine IS http.Handler) |
| beego | app.Mount("/legacy/iam", beeApp.Handlers) |
Each adapter is doc'd "MIGRATION TOOL — costs ~5% perf vs native Fiber dispatch. Replace with native handlers when feasible."
See docs/MIGRATION.md for per-framework recipes.
License
Apache-2.0 (carry-forward from upstream zeekay/zip license metadata).