Documentation
¶
Overview ¶
Package zip is Hanzo's canonical Go web framework. Built on Fiber v3 / fasthttp. ZAP-typed handlers. Multi-language extension support via HIP-0105.
ONE framework, ZERO escape hatches. zip IS fast.
app := zip.New(zip.Config{Logger: luxlog.NewLogger("svc")})
app.Use(middleware.Recover(), middleware.RequestID())
app.Get("/health", func(c *zip.Ctx) error {
return c.JSON(200, fiber.Map{"ok": true})
})
app.Listen(":9653", "http://:8080") // ZAP primary + HTTP extra, one verb
Public surface — types/functions exposed at the package root:
type App, Config, Ctx, Handler func New(Config) *App func Get[I, O](app *App, path string, fn func(ctx, *I) (*O, error)) func Post[I, O](app *App, path string, fn func(ctx, *I) (*O, error)) ...
All other behavior lives in subpackages: `middleware`, `runtime`.
Index ¶
- Constants
- func Addr(fallback string) string
- func Delete[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
- func Describe(methodPath string, d Doc)
- func Get[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
- func LocalInvoke(ctx context.Context, c Command, path map[string]string, body []byte) (any, error)
- func Patch[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
- func Post[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
- func Put[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
- func RegisterTransport(scheme string, t Transport)
- type App
- func (a *App) Add(services ...Service) error
- func (a *App) All(path string, handlers ...Handler) Router
- func (a *App) Authorize(fn Authorizer)
- func (a *App) CLI() *CLI
- func (a *App) Commands() []Command
- func (a *App) Delete(path string, handlers ...Handler) Router
- func (a *App) Fiber() *fiber.App
- func (a *App) Get(path string, handlers ...Handler) Router
- func (a *App) Group(prefix string, handlers ...Handler) Router
- func (a *App) Head(path string, handlers ...Handler) Router
- func (a *App) Listen(addrs ...string) error
- func (a *App) Logger() luxlog.Logger
- func (a *App) Module(methodPath, runtimeName, modulePath string) error
- func (a *App) Mount(prefix, addr string) error
- func (a *App) OnShutdown(fn func(context.Context) error)
- func (a *App) OpenAPISpec() map[string]any
- func (a *App) Options(path string, handlers ...Handler) Router
- func (a *App) Patch(path string, handlers ...Handler) Router
- func (a *App) Plugins() []PluginStatus
- func (a *App) Post(path string, handlers ...Handler) Router
- func (a *App) Prepare()
- func (a *App) Put(path string, handlers ...Handler) Router
- func (a *App) Reload(name string, bin []byte) error
- func (a *App) Shutdown() error
- func (a *App) ShutdownWithContext(ctx context.Context) error
- func (a *App) TestCtx(method, path string) *Ctx
- func (a *App) Unload(name string) error
- func (a *App) Use(handlers ...Handler) Router
- func (a *App) With(mw ...Middleware) Router
- type Arg
- type Authorizer
- type CLI
- type Client
- type Command
- type Config
- type Ctx
- func (c *Ctx) App() *App
- func (c *Ctx) Bind(v any) error
- func (c *Ctx) BindQuery(v any) error
- func (c *Ctx) BindURI(v any) error
- func (c *Ctx) Body() []byte
- func (c *Ctx) Bytes(code int, b []byte) error
- func (c *Ctx) Context() context.Context
- func (c *Ctx) Continue() error
- func (c *Ctx) Fiber() fiber.Ctx
- func (c *Ctx) Header(name string) string
- func (c *Ctx) Host() string
- func (c *Ctx) IsAdmin() bool
- func (c *Ctx) JSON(code int, v any) error
- func (c *Ctx) Locals(key any, value ...any) any
- func (c *Ctx) Log() luxlog.Logger
- func (c *Ctx) Method() string
- func (c *Ctx) Next() error
- func (c *Ctx) NoContent(code int) error
- func (c *Ctx) Org() string
- func (c *Ctx) Param(name string) string
- func (c *Ctx) Path() string
- func (c *Ctx) Query(name string) string
- func (c *Ctx) Redirect(code int, location string) error
- func (c *Ctx) RequestID() string
- func (c *Ctx) SendStream(r io.Reader) error
- func (c *Ctx) SendStreamWriter(fn func(w *bufio.Writer)) error
- func (c *Ctx) SetContext(ctx context.Context)
- func (c *Ctx) SetHeader(name, value string)
- func (c *Ctx) SetLog(l luxlog.Logger)
- func (c *Ctx) Status(code int) *Ctx
- func (c *Ctx) String(code int, s string) error
- func (c *Ctx) User() string
- func (c *Ctx) UserEmail() string
- type Doc
- type Flag
- type HTTPError
- func ErrBadRequest(msg string) *HTTPError
- func ErrConflict(msg string) *HTTPError
- func ErrForbidden(msg string) *HTTPError
- func ErrInternal(msg string) *HTTPError
- func ErrNotFound(msg string) *HTTPError
- func ErrUnauthorized(msg string) *HTTPError
- func Errorf(status int, format string, args ...any) *HTTPError
- type Handler
- type Invoker
- type MCPConfig
- type Middleware
- type Op
- type OpOption
- type OpenAPIConfig
- type Plugin
- type PluginStatus
- type Remote
- type Router
- type Server
- type Service
- type StaticOption
- type Transport
- type TypedHandler
- type Usage
Constants ¶
const AddrEnv = "ZIP_ADDR"
AddrEnv is the variable a host sets to tell a plugin where to listen. A plugin reads it through Addr.
const DefaultScheme = "zap"
DefaultScheme is the transport a bare address (no "scheme://") uses. ZAP is the primary transport (TLS 1.3 + post-quantum, gRPC's replacement), so the path of least resistance is ZAP-native.
const JSONVariant = jsonenc.Variant
JSONVariant reports which JSON implementation zip is using in this build — "encoding/json/v2" when compiled with GOEXPERIMENT=jsonv2, "encoding/json" otherwise. Exposed for cmd/cloud startup logs and for tests that need to assert the variant. Per HIP-0106 the wire stack is "JSON only at edge, ZAP between services"; this constant tells operators which JSON impl is on the edge.
Variables ¶
This section is empty.
Functions ¶
func Addr ¶ added in v1.9.0
Addr returns the address this process was asked to serve on, or fallback when it was started directly rather than by a host. This is the whole plugin side of the contract.
func Delete ¶
func Delete[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
Delete registers a DELETE typed handler at path.
func Describe ¶ added in v1.17.0
Describe records documentation for one operation. Generated code calls it from an init(); hand-written calls are possible but defeat the point, since the comment is then no longer the single source.
func Get ¶
func Get[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
Get registers a GET typed handler at path.
func LocalInvoke ¶ added in v1.17.2
LocalInvoke runs the handler in this process, through the op's own invoke seam. It is the whole reason a fused binary needs no client: the command IS the handler call.
func Patch ¶
func Patch[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
Patch registers a PATCH typed handler at path.
func Post ¶
func Post[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
Post registers a POST typed handler at path.
func Put ¶
func Put[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption)
Put registers a PUT typed handler at path.
func RegisterTransport ¶ added in v1.1.0
RegisterTransport adds (or replaces) a transport keyed by address scheme, so any future protocol slots into both Listen and Mount with ZERO change to either API. Call before Listen or Mount.
zip.RegisterTransport("quic", zip.Transport{
Serve: func(addr string, h fasthttp.RequestHandler) zip.Server {
return myquic.NewServer(addr, h)
},
Dial: func(addr string) zip.Client { return myquic.NewClient(addr) },
})
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the zip application. It wraps *fiber.App and exposes the zip handler signature alongside generic typed handlers.
func New ¶
New constructs an App with the given config. Defaults are applied for any zero-valued field.
func (*App) Add ¶ added in v1.9.0
Add composes services into the app in order, stopping at the first error and naming which position failed. Order matters exactly as much as it does for hand-written route registration: earlier routes are registered earlier, and specificity still decides which one answers.
func (*App) Authorize ¶ added in v1.8.3
func (a *App) Authorize(fn Authorizer)
Authorize installs fn as the op-invoke authorization hook. It is the op-level counterpart to Use: Use wraps the whole request with transport middleware, which for a body request sees only the raw bytes; Authorize runs one decision on the DECODED typed input of every op, REST and MCP alike — the seam a mounted subsystem gates on so the value it authorizes is the value the handler binds. Call once while mounting, before Listen. A nil fn clears it (every decoded request then runs unauthorized).
func (*App) CLI ¶ added in v1.17.2
CLI returns the command line for everything registered on this app, executed in-process. `app.CLI().Run(ctx, os.Args[1:])` is a complete CLI for a service.
func (*App) Commands ¶ added in v1.17.2
Commands projects every registered typed op into a command. This is the whole derivation: no registration, no list of commands, no per-endpoint code.
func (*App) Fiber ¶
Fiber returns the underlying *fiber.App. Use for one-off escape into Fiber-only APIs (rare). Prefer staying on the zip surface.
func (*App) Get ¶
Get / Post / Put / Patch / Delete / Head / Options / All register routes. Chains are in wrapping order: middleware first, the final handler last.
func (*App) Group ¶
Group creates a path-prefixed router group. The returned Router is the one way to register nested routes under a prefix — register leaves and further Groups directly on it; middleware scoped to the group goes on via its Use.
func (*App) Listen ¶ added in v1.1.0
Listen serves the app on one or more addresses and blocks until all listeners stop or the first one errors. The address scheme selects the transport; a bare address uses ZAP (DefaultScheme). This is the ONE and only way to serve a zip app — no per-transport methods.
func (*App) Module ¶
Module mounts a single HIP-0105 extension at the given method+path — the one way to put an extension on the app. The `methodPath` form is "METHOD /path" (e.g. "POST /v1/validate"), matching the idiom. `runtime` selects the backing engine ("wasm" | "goja" | "pyvm" | "starlark" | "v8go" | "native"); `modulePath` is the directory containing the extension.json manifest.
app.Module("POST /v1/policy/eval", "wasm", "./extensions/policy")
app.Module("POST /v1/transform", "pyvm", "./extensions/transform")
app.Module("POST /v1/webhook", "goja", "./extensions/webhook")
The extension's exported function name is inferred from the path's last non-{param} segment, lowercased (e.g. "POST /v1/policy/eval" → "eval").
func (*App) Mount ¶
Mount delegates every request under prefix to the service at addr. Listen serves here; Mount delegates there — one registry, one scheme vocabulary, opposite directions. A bare address uses ZAP (DefaultScheme), so a colocated service is mounted by address alone:
app.Mount("/v1/billing", "billing.hanzo.svc:9653") // ZAP
app.Mount("/v1/legacy", "http://legacy.internal:8080") // HTTP
The mounted service keeps the whole path — /v1/billing/invoices arrives as /v1/billing/invoices — so its routes ARE the surface, exactly as they are when it is linked in. Nothing is re-encoded in between: the inbound fasthttp request object is handed to the client and the reply is written straight back, so a mount costs a connection, not a copy.
This is how a service ships as its own binary. The mounting binary links zip and a transport, never the mounted service's dependency graph, so its link time does not grow when that service does, and the service redeploys without relinking it. Moving a service between Mount and a linked-in route tree is a deployment decision, not a code change.
func (*App) OnShutdown ¶ added in v1.4.0
OnShutdown registers fn as a teardown hook, run during Shutdown / ShutdownWithContext. This is the one teardown primitive zip exposes: subsystems register their own cleanup at mount time, and reverse-mount teardown falls out for free (see the ordering note below).
Ordering. Hooks run LAST in the shutdown sequence — after listeners stop accepting and after in-flight requests drain — and in LIFO order (reverse registration = reverse mount order). Draining first means a subsystem's teardown never races the requests still using it; LIFO means a dependency mounted before its dependents is torn down after them.
Errors. Every hook runs even if an earlier one fails; all hook errors (and the drain error) are aggregated with errors.Join and returned from Shutdown.
Concurrency. Registration is safe from multiple goroutines. A nil fn is ignored. Registering after Shutdown has begun is a no-op: the hook is dropped (never run) and a warning is logged — there is no longer a shutdown to hook into, and running it immediately would give OnShutdown two meanings depending on timing. Register teardown at mount time, before Shutdown.
func (*App) OpenAPISpec ¶ added in v1.10.3
OpenAPISpec returns the OpenAPI 3.1 document for every typed op registered on this app — the SAME value served at /.well-known/openapi.json.
It is exported so a service can render its published contract from the routes it actually registers, in a build step rather than from a running server. A spec generated any other way is a second source of truth, and the whole point of deriving it here is that there is only one.
func (*App) Plugins ¶ added in v1.13.0
func (a *App) Plugins() []PluginStatus
Plugins reports every plugin this host has loaded, ordered by name so a diff between two hosts is stable. Safe to call while requests are in flight.
func (*App) Prepare ¶ added in v1.8.3
func (a *App) Prepare()
Prepare installs the deferred projections (the OpenAPI document and the MCP tool surface) without starting a listener, so a test can drive them through Fiber().Test exactly as a served app exposes them. Listen calls it too; both share one guard, so it runs at most once however it is reached.
func (*App) Reload ¶ added in v1.9.0
Reload replaces the running plugin named name with a new build, without dropping a request. bin is the new binary; nil reuses what the plugin was loaded with, which is how you restart a crashed or wedged one.
The new process is started and proven to be listening before any request moves to it. If it fails to come up, the old one is still serving and the error is returned — a bad build cannot take the route down. Once the swap happens the old process keeps serving for Plugin.Drain so in-flight requests finish, then is killed, reaped, its connections closed and its directory removed.
func (*App) Shutdown ¶
Shutdown gracefully stops every transport, then runs teardown hooks. The process is ending, so hooks receive context.Background() — no cancellation or deadline. Use ShutdownWithContext to bound teardown. Idempotent: a second call is a no-op and hooks run at most once.
func (*App) ShutdownWithContext ¶
ShutdownWithContext is Shutdown bounded by ctx: ctx bounds the in-flight drain and is passed to every teardown hook (values and deadline). Shares Shutdown's once-guard, so mixing the two still runs hooks once.
func (*App) TestCtx ¶ added in v1.7.1
TestCtx returns a detached *Ctx over a synthetic request — the unit-test analog of a live request context, for calling a Handler directly. Integration tests should prefer app.Fiber().Test(req), which exercises routing and the full middleware chain; this exists for the narrower "call this one handler with locals seeded" idiom. The Ctx is not pooled; do not release it.
func (*App) Unload ¶ added in v1.9.0
Unload stops the plugin named name. Its routes stay registered and answer 503 until a Reload brings it back — the route table is never mutated, which is what keeps repeated load/unload cycles flat.
func (*App) Use ¶
Use registers zip-style middleware. Each Handler runs in order; calling c.Next() (via c.Continue) chains to the next handler.
func (*App) With ¶ added in v1.5.0
func (a *App) With(mw ...Middleware) Router
With returns a Router whose subsequent leaf registrations (Get/Post/…/All) have mw wrapped around the handler at registration time — pure composition (RateLimit(CSRF(handler))). It does NOT touch the global Use stack and does NOT route through c.Next(); it is the per-route counterpart to Use. Routes registered on the returned Router still obey specificity precedence exactly like any other route.
app.With(RateLimit, CSRF).Post("/v1/keys", mintKey)
type Authorizer ¶ added in v1.8.3
Authorizer authorizes a decoded, validated typed request at the op-invoke seam — the ONE point every projection of a typed handler funnels through. It runs after the request is decoded into the op's typed In and validated, and BEFORE the handler runs, over REST and MCP alike, so the value it authorizes is exactly the value the handler will act on: there is no second parse of the body for it to diverge from. in is the *In the handler will receive.
Returning a non-nil error aborts the op before the handler runs, and that error is the response — return a zip.Err* (e.g. ErrForbidden) for a clean status.
type CLI ¶ added in v1.17.2
CLI runs a derived command tree. Name is what the usage lines call the binary; Invoke defaults to LocalInvoke and Out to os.Stdout's stand-in the caller passes.
type Client ¶ added in v1.8.4
Client is the call side of a transport: anything that can complete a request. *zaphttp.Transport and *fasthttp.HostClient already satisfy it, so giving a scheme a Dial is a one-liner.
type Command ¶ added in v1.17.2
type Command struct {
// Service is the first non-version segment of the path ("billing"), and
// Name is the operation token under it ("invoices-list").
Service string
Name string
// Summary is the one-line help; Description is the full prose. Both come
// from the handler's doc comment when cmd/zipdoc has run.
Summary string
Description string
// Method and Path are the operation's identity — the route pattern in
// fiber's ":name" form, whatever the derivation read it from.
Method string
Path string
// Args are the path parameters, in path order, as positional arguments. The
// URL is the addressing authority (see bindPath), so what addresses the
// resource is positional and what modifies the request is a flag.
Args []Arg
// Flags are the remaining In fields.
Flags []Flag
// Example is the request body from the doc comment, rendered by the help as
// a runnable command line.
Example json.RawMessage
// contains filtered or unexported fields
}
Command is one operation as a command line: the value both the help text and the argument parser read. It is the CLI's whole surface — a derivation fills it in, the runner consumes it, and neither knows how the other got there.
func CommandsFromSpec ¶ added in v1.17.2
CommandsFromSpec derives the same command tree from an OpenAPI document — which is the registry in wire form, generated by buildOpenAPI from those very ops. It is what lets a client CLI be derived without linking the service: ask a running one what it can do, and every command it offers is a projection of a route that service actually registered.
The two derivations meet in the same Command value and are read by the same runner, so a command spelled locally and a command spelled over the wire are the same command. TestCLI_SpecAndRegistryAgree pins that.
type Config ¶
type Config struct {
// Logger is the luxfi/log Logger zip uses internally. Required.
// If nil, a default one is created via luxlog.NewLogger("zip").
Logger luxlog.Logger
// Loader is the HIP-0105 extension runtime loader. nil disables
// app.Module() — only native handlers will work. The interface is
// satisfied by *extruntime.Loader from hanzoai/base/plugins/extruntime;
// zip does NOT take a hard dep on hanzoai/base.
Loader runtime.Loader
// AllowedRuntimes restricts which extension runtimes app.Module()
// will accept (e.g. ["goja","wazero"] for hard multi-tenant safety).
// nil = allow whatever the Loader has registered.
AllowedRuntimes []string
// ServerHeader is sent as the Server: response header (default "zip").
// Set to "-" to suppress.
ServerHeader string
// BodyLimit is the maximum request body size (default 4 MiB).
BodyLimit int
// AppName forwards to fiber.Config.AppName.
AppName string
// DisableStartupMessage suppresses Fiber's startup banner.
DisableStartupMessage bool
// ErrorHandler is the catch-all error handler. Defaults to zip.errorHandler
// which renders {error, code, status} JSON.
ErrorHandler fiber.ErrorHandler
// Concurrency caps the maximum number of concurrent connections the
// server will accept. Default 0 means fasthttp's own default
// (256*1024). Ops should cap this at the per-replica budget — see
// `~/work/hanzo/hips/docs/SCALE_STANDARD.md`. With Hanzo's verified
// 8 KiB/conn budget, 100_000 sits at ~800 MiB inside a 1 GiB pod.
Concurrency int
// ReadBufferSize is fasthttp's per-conn request-read buffer (default
// 4 KiB). Raise only for header-heavy upstreams; raising it inflates
// the per-conn memory budget and breaks the conn-memory regression
// gate (see SCALE_STANDARD.md §8).
ReadBufferSize int
// WriteBufferSize is fasthttp's per-conn response-write buffer
// (default 4 KiB). Raise only for streaming-heavy responses; same
// budget caveat as ReadBufferSize.
WriteBufferSize int
// OpenAPI configures the auto-generated /.well-known/openapi.json
// served when typed handlers are registered.
OpenAPI OpenAPIConfig
// MCP configures the Model Context Protocol tool surface auto-derived from
// typed handlers (Get/Post[In,Out]). Enabled by default — it's free (the
// same op registry that feeds OpenAPI), served over every transport. Set
// MCP.Disabled to suppress.
MCP MCPConfig
}
Config configures the zip App. Most fields pass through to Fiber's own Config; a few zip-specific knobs control runtime loading.
type Ctx ¶
type Ctx struct {
// contains filtered or unexported fields
}
Ctx wraps fiber.Ctx and adds the Hanzo identity surface (Org/User/Email from gateway-minted X-* headers per HIP-0026), a per-request luxfi/log logger, and typed Deps access.
func (*Ctx) Bind ¶
Bind parses the request body into v based on Content-Type (JSON by default) and runs struct-tag validation (required/min/max/minlen/maxlen). Returns a *HTTPError(400) when either step fails so handlers can return the error directly.
func (*Ctx) Host ¶ added in v1.7.3
Host returns the request Host (authority) from the Host header, port included when present. It honors X-Forwarded-Host ONLY when the app is configured to trust proxies — which zip does NOT do (there is no TrustProxy knob on zip.Config), so a client-supplied X-Forwarded-Host is ignored and cannot spoof the value. Used for white-label brand-by-host resolution (see middleware.ProductionHeaders); keep it un-trusted-proxy so the Server brand cannot be forged from a request header.
func (*Ctx) Log ¶
Log returns the request-scoped logger. Middleware that adds request_id, org, user, etc. via Locals can enrich this by calling SetLog.
func (*Ctx) Next ¶
Next yields to the next handler in the chain. Use sparingly from zip middleware — middleware bodies usually call c.Continue() at the end, not Next() mid-handler.
func (*Ctx) Org ¶
Org returns the X-Org-Id from the JWT-validated gateway. Empty when no gateway is in front (local dev / direct ingress).
func (*Ctx) Redirect ¶ added in v1.7.0
Redirect sends an HTTP redirect to location with the given status code.
func (*Ctx) RequestID ¶
RequestID returns the value of X-Request-Id (set by the RequestID middleware).
func (*Ctx) SendStream ¶
SendStream streams data from r to the client (e.g. for SSE).
func (*Ctx) SendStreamWriter ¶
SendStreamWriter writes streaming output via a bufio.Writer (Server-Sent Events / chunked transfer). Forwards to fiber.Ctx.SendStreamWriter.
func (*Ctx) SetContext ¶ added in v1.7.3
SetContext replaces the request's context.Context — the boundary idiom: a middleware derives a request-scoped context (values, gates, deadlines) ONCE and every later c.Context() returns it. One context per request, one setter.
func (*Ctx) SetLog ¶
SetLog replaces the request logger (typically by middleware that wants to attach request-id / org / user fields).
type Doc ¶ added in v1.17.0
type Doc struct {
// Description is the handler's doc comment, minus any Example/Response lines.
Description string
// Fields maps a JSON field name to its doc comment, for both In and Out.
// Keyed by "TypeName.jsonField" so an In and an Out field of the same name
// do not collide.
Fields map[string]string
// Example and Response are the request and response bodies from the comment.
// Raw JSON so they land in the spec exactly as written and are wrong loudly
// rather than quietly if malformed.
Example json.RawMessage
Response json.RawMessage
}
Doc is what cmd/zipdoc extracted for one operation.
type Flag ¶ added in v1.17.2
type Flag struct {
Name string // the flag, kebab-cased and without the dashes: "organization-id"
Field string // the JSON field it sets: "organizationId"
Type string // string | integer | number | boolean | json
Help string
Required bool
}
Flag is one In field as a flag.
type HTTPError ¶
type HTTPError struct {
Status int `json:"status"`
Code string `json:"code,omitempty"`
Msg string `json:"error"`
}
HTTPError is the canonical error type zip understands. Returning one causes the error handler to send a JSON {error, code, status} body.
func ErrConflict ¶
func ErrForbidden ¶
func ErrInternal ¶
func ErrNotFound ¶
func ErrUnauthorized ¶
type Handler ¶
Handler is zip's request handler signature. Returning an error causes Fiber's error chain to write a JSON response.
func AdaptNetHTTP ¶
AdaptNetHTTP wraps an http.Handler so it can be served on a zip router as an ordinary zip.Handler. To front a whole foreign subtree, take a Group for the prefix and register a wildcard on it — this is THE way to bring net/http code onto zip:
app.Group("/legacy").All("/*", zip.AdaptNetHTTP(httpHandler))
Group is what carries the prefix; Mount does not do this. Mount delegates a prefix to a REMOTE address over a transport, so it takes a string, not a handler. A local handler is composed, not delegated.
The wildcard stays an ordinary route, so most-specific-wins precedence still applies: a route registered on the group afterwards still beats it for its exact path.
g := app.Group("/legacy")
g.All("/*", zip.AdaptNetHTTP(httpHandler)) // everything else
g.Get("/health", nativeHealth) // still wins
A bare func is adapted through http.HandlerFunc, which is itself an http.Handler — there is no separate func-shaped adapter:
app.Get("/legacy", zip.AdaptNetHTTP(http.HandlerFunc(myFunc)))
Migration tool — costs ~5% perf vs native Fiber. Replace with native zip handlers when feasible.
func AdaptNetHTTPMiddleware ¶
AdaptNetHTTPMiddleware wraps a stdlib middleware (func(http.Handler) http.Handler) as a zip.Handler — the net/http-middleware bridge. Because it IS middleware, it goes on a Group's Use rather than on a route:
app.Group("/legacy").Use(zip.AdaptNetHTTPMiddleware(mw))
Migration tool — costs ~5% perf vs native Fiber.
func Static ¶ added in v1.5.0
func Static(fsys fs.FS, opts ...StaticOption) Handler
Static returns a leaf Handler that serves files from fsys. Mount it on a wildcard route; the "*" capture selects the file:
app.Get("/assets/*", zip.Static(assets)) // embed.FS
app.Get("/app/*", zip.Static(os.DirFS("dist"), zip.WithIndex("index.html")))
Contract:
- The subpath is cleaned and checked with fs.ValidPath; any ".." escape or absolute path is rejected fail-closed with 404 — Static can never read outside fsys.
- A missing file yields c.Next(), so a later more-specific route or a SPA catch-all still wins — never a 500.
- Sets Content-Type (by extension), Content-Length and Last-Modified; honours HEAD and If-Modified-Since (304). Nothing else — no compression, no byte ranges, no directory listing.
fsys is any fs.FS: an embed.FS for baked-in assets or os.DirFS(dir) for a directory on disk. Both are traversal-safe by construction; the fs.ValidPath gate is defence in depth on top of that.
type Invoker ¶ added in v1.17.2
Invoker performs one parsed command. path holds the positional arguments by parameter name and body is the JSON built from the flags — exactly what a typed op's invoke seam takes, so a command runs through the same decode, validate and authorize path as a REST request or an MCP tool call.
type MCPConfig ¶ added in v1.1.0
type MCPConfig struct {
// Disabled suppresses the /mcp route (MCP is on by default — it's free).
Disabled bool
// Path overrides the mount path (default "/mcp").
Path string
// Name is the server name reported to MCP clients (default AppName, else "zip").
Name string
}
MCPConfig configures the auto-derived MCP surface.
type Middleware ¶ added in v1.5.0
Middleware is a composable request transformer in the classic wrapping form: given the next Handler it returns a Handler that runs around it. This is a DIFFERENT tool from Use — they do different jobs and compose freely:
Use(Handler...) registers GLOBAL / prefix middleware. It runs for every matched route (or every route under a Group) in DECLARATION order and chains via c.Next(). Reach for it for ambient cross-cutting concerns that apply broadly: logging, recovery, request-id.
Middleware + With + Chain wrap ONE leaf handler explicitly, at registration time, with no c.Next() indirection. Reach for it when a specific endpoint needs a specific pipeline:
app.With(RateLimit, CSRF).Post("/v1/keys", mintKey)
wraps mintKey as RateLimit(CSRF(mintKey)): RateLimit is outermost and runs first, CSRF next, the handler last; any layer short-circuits by returning without calling next.
A Middleware body is written by hand, no framework glue:
func RequireCSRF(next zip.Handler) zip.Handler {
return func(c *zip.Ctx) error {
if !validCSRF(c) {
return c.String(403, "bad csrf") // short-circuit
}
return next(c) // continue
}
}
func Chain ¶ added in v1.5.0
func Chain(mw ...Middleware) Middleware
Chain composes middleware left-to-right into one Middleware. Chain(a, b, c) nests as a(b(c(handler))): a is outermost (runs first inbound, last outbound), c innermost, wrapping the handler directly. Chain() with no arguments is the identity middleware.
type Op ¶ added in v1.8.3
Op is the stable identity of a registered typed handler, handed to an Authorizer so the decision can key on the operation as well as the input. OperationID is the resolved id the OpenAPI document and the MCP tool surface share — the explicit WithOperationID, else the method+path default.
type OpOption ¶
type OpOption func(*registeredOp)
OpOption configures a typed handler registration (OpenAPI metadata).
func WithOperationID ¶
WithOperationID sets the operation ID in OpenAPI.
func WithSummary ¶
WithSummary sets the operation summary in OpenAPI.
type OpenAPIConfig ¶
type OpenAPIConfig struct {
// Title appears in the OpenAPI info block.
Title string
// Description appears in the OpenAPI info block.
Description string
// Version appears in the OpenAPI info block (e.g. "v1.0.0").
Version string
// Disabled suppresses the /.well-known/openapi.json route and /docs.
Disabled bool
}
OpenAPIConfig configures the auto-generated /.well-known/openapi.json endpoint zip serves when typed handlers are registered.
type Plugin ¶ added in v1.9.0
type Plugin struct {
Name string // identifies it in log lines and names its socket
Addr string // already running here — start nothing, just mount
Bin []byte // the binary, normally go:embed'd
Path string // ...or where it lives on disk
URL string // ...or a release artifact to fetch (requires Sum)
Args []string // passed after argv[0]
Env []string // added to the child's environment
// Sum is the hex SHA-256 of the binary at URL, and is REQUIRED with it.
// Fetching code over a network and executing it is the one place a plugin
// host becomes an arbitrary-code-execution vector, so an unverified
// download is refused rather than trusted.
//
// It doubles as the cache key: a binary already present under this digest
// is reused, so a restart costs no download and a rollback to a previously
// run version is free and offline.
Sum string
// Dir is where an embedded binary is extracted and its socket created.
// Empty means the system temp dir, which on many hosts is a tmpfs — i.e.
// RAM. A plugin binary is tens to hundreds of megabytes, so extracting one
// there spends real memory and fails outright when the tmpfs is full. Point
// this at disk for anything but a small plugin.
Dir string
// Start bounds how long to wait for the plugin to listen. Zero means 10s.
// A plugin that has not bound by then is a startup failure, not a slow
// one — nothing is mounted onto a process that never came up.
Start time.Duration
// Drain is how long a replaced process keeps serving after a Reload, so
// requests already in flight on it finish. Zero means 5s.
Drain time.Duration
// Lazy defers starting the child until the first request actually reaches
// one of its prefixes. Routes register at Load either way, so the surface
// is identical — only the process is deferred.
//
// This is what makes many plugins affordable. A host composing 69 services
// eagerly pays 69 processes, 69 resident sets and 69 startup times at boot
// for a set that is mostly idle; lazily it pays for the ones traffic
// actually reaches. The cost moves to the first request, which is why it is
// opt-in: a latency-critical prefix should stay eager.
Lazy bool
}
Plugin is a service that ships as its own binary. Exactly one of Addr, Bin, or Path says where to find it:
Addr — already running there; nothing is started, and Reload does not apply Bin — the binary itself, normally go:embed'd Path — the binary on disk
type PluginStatus ¶ added in v1.13.0
type PluginStatus struct {
Name string `json:"name"`
// Prefix is the FIRST subtree this plugin answers — the one a log line
// names it by. Prefixes is every subtree, and a plugin may own several.
// Reporting only the first would understate the blast radius of taking
// this plugin down, which is the question a fleet view exists to answer.
Prefix string `json:"prefix"`
Prefixes []string `json:"prefixes,omitempty"`
// Source is where the binary came from: "embedded", "path", "url", or
// "remote" for an instance this host did not start.
Source string `json:"source"`
// Version is the artifact's SHA-256 when it was installed from a URL —
// the only version identifier that cannot drift from the bits actually
// running, since it IS the bits. Empty for the other sources.
Version string `json:"version,omitempty"`
// Addr is the socket or address serving it.
Addr string `json:"addr,omitempty"`
// PID is the child process, or 0 when this host did not start it.
PID int `json:"pid,omitempty"`
// Running is false after Unload, or after a child exited and no Reload has
// replaced it. Its routes stay registered and answer 503, so a false here
// is the difference between "not deployed" and "deployed but down".
Running bool `json:"running"`
// Since is when the CURRENT instance started — it resets on Reload, so it
// reports the age of what is running, not of the mount.
Since time.Time `json:"since,omitzero"`
// Reloads counts successful swaps since Load. A climbing number on one
// host and not its peers is the signal that a rollout is uneven.
Reloads int `json:"reloads"`
// Restarts counts times the supervisor brought this plugin back after it
// died on its own. Distinct from Reloads, which are deliberate: a nonzero
// Restarts is a plugin crashing, and a climbing one is a crash loop.
Restarts int `json:"restarts"`
// Usage is what this plugin costs right now, read from the kernel.
Usage Usage `json:"usage,omitzero"`
}
PluginStatus is one loaded plugin as the host currently sees it.
type Remote ¶ added in v1.17.2
type Remote struct {
Base string // "https://api.hanzo.ai", "http://:8080", a unix socket path…
Header map[string]string // sent on every request (Authorization, …)
}
Remote executes a command against a running zip service, and reads that service's registry off the document it derives from it. Base is an address in the same form Mount takes — the scheme selects the transport, so a command runs over ZAP, HTTP or anything else RegisterTransport'd without the CLI knowing which.
type Router ¶
type Router interface {
Use(handlers ...Handler) Router
// Route registration takes ONE chain in wrapping order: zero or more
// middleware first, the final handler LAST. fiber wants handler-first;
// splitChain flips it in exactly one place.
Get(path string, handlers ...Handler) Router
Post(path string, handlers ...Handler) Router
Put(path string, handlers ...Handler) Router
Patch(path string, handlers ...Handler) Router
Delete(path string, handlers ...Handler) Router
Head(path string, handlers ...Handler) Router
Options(path string, handlers ...Handler) Router
All(path string, handlers ...Handler) Router
Group(prefix string, handlers ...Handler) Router
// Fiber returns the underlying fiber.Router for one-off escape.
Fiber() fiber.Router
}
Router is the path-mounting surface shared by *App and Group. All concrete routes flow through toFiberHandler — fiber.Ctx is the underlying type the framework's users never see directly.
type Server ¶ added in v1.1.0
Server is a running transport listener bound to one address. Both zap-proto/http.Server and the built-in HTTP server satisfy it, as does any custom transport.
type Service ¶ added in v1.9.0
Service is a unit of functionality that attaches itself to an app: its routes, its middleware, its shutdown hooks. One type covers every way a unit can be composed, which is what makes where it runs a deployment decision instead of a code change:
app.Add(
billing.New(deps), // linked into this binary
zip.Load("/v1/search", zip.Plugin{Name: "search", // its own binary, embedded
Bin: searchBin}),
zip.Load("/v1/ml", zip.Plugin{Name: "ml", // already running elsewhere
Addr: os.Getenv("ML_ADDR")}),
)
A constructor that takes dependencies and returns a Service is the same thing curried — func(deps) Service — so a unit's dependencies are bound where they are known and the composition root only ever sees Service.
func Load ¶ added in v1.9.0
Load returns the Service that makes p serve prefix. When p.Addr is set it is mounted directly. Otherwise the binary is started as a child process listening on its own unix socket in a private 0700 directory — no port to allocate, and filesystem permissions are the ACL.
It returns a Service rather than taking an *App so a plugin and a linked-in service have the SAME type, and composing either is the same line.
The child is stopped and its directory removed on Shutdown, in LIFO order with every other hook, so a host that exits cleanly leaves nothing behind. prefixes is variadic because a service often owns more than one route subtree — o11y answers both /v1/o11y and /v1/sentry — and a single-prefix Load silently 404s the others. One call declares everything the plugin owns.
type StaticOption ¶ added in v1.5.0
type StaticOption func(*staticConfig)
StaticOption configures Static. Zero options serve fsys as-is.
func WithFallback ¶ added in v1.7.2
func WithFallback(name string) StaticOption
WithFallback serves name (e.g. "index.html") when the requested file does not exist — the SPA deep-link idiom: client-side routes resolve to the app shell instead of 404/next-route. Traversal-invalid paths still fail closed.
func WithIndex ¶ added in v1.5.0
func WithIndex(name string) StaticOption
WithIndex serves name (e.g. "index.html") for directory and root requests. Without it a directory request falls through via c.Next().
func WithStripPrefix ¶ added in v1.5.0
func WithStripPrefix(prefix string) StaticOption
WithStripPrefix derives the fs path from the request path with prefix removed, instead of from the route's "*" capture. Use it when the captured subpath is not the fs path — e.g. a versioned URL served from an unversioned tree: WithStripPrefix("/static/v2/") maps /static/v2/app.js to app.js.
type Transport ¶ added in v1.8.4
type Transport struct {
Serve func(addr string, handler fasthttp.RequestHandler) Server
Dial func(addr string) Client
}
Transport is one address scheme in both directions: Serve terminates bytes arriving here, Dial originates bytes going there. They are the same concept — a wire — so they live in one value rather than two registries that drift. A scheme may leave either half nil; Listen and Mount each say which they needed.
type TypedHandler ¶
TypedHandler is the generic handler signature: takes an *In, returns (*Out, error). zip generates OpenAPI 3.1 spec from the In/Out types and registers a Fiber route that unmarshals body → In, runs the handler, and marshals Out → JSON response.
type Usage ¶ added in v1.15.0
type Usage struct {
// CPU is total user+system time this instance has consumed since it
// started. It only ever climbs, so a rate is the interesting derivative.
CPU time.Duration `json:"cpuNs,omitzero"`
// RSS is resident memory in bytes — the honest number for "what does this
// plugin cost", as opposed to virtual size.
RSS int64 `json:"rssBytes,omitzero"`
// Threads and FDs are the two limits a busy service hits first, and both
// are leaks when they climb without bound.
Threads int `json:"threads,omitzero"`
FDs int `json:"fds,omitzero"`
}
Usage is a plugin's resource cost, measured rather than self-reported.
This exists because a plugin is a process. A linked-in subsystem cannot be measured this way at any price — one process has one RSS and one CPU clock, and nothing inside it can be attributed to a subsystem. Running a service as its own process makes "which app is eating the memory" a question with an exact answer instead of an argument.
Zero means not measured (no /proc, or nothing running), not measured-as-zero.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
zipdoc
command
Command zipdoc makes a doc comment the spec.
|
Command zipdoc makes a doc comment the spec. |
|
examples
|
|
|
express-in-zip
command
express-in-zip — the proof point: a legacy Express-shaped TypeScript handler running inside zip with ZERO rewrite.
|
express-in-zip — the proof point: a legacy Express-shaped TypeScript handler running inside zip with ZERO rewrite. |
|
hello
command
Hello, zip — minimal example.
|
Hello, zip — minimal example. |
|
migrate-from-beego
command
migrate-from-beego example — beego → zip migration via http.Handler adapter.
|
migrate-from-beego example — beego → zip migration via http.Handler adapter. |
|
migrate-from-chi
command
migrate-from-chi example — chi → zip via stdlib adapter.
|
migrate-from-chi example — chi → zip via stdlib adapter. |
|
migrate-from-gin
command
migrate-from-gin example — mechanical port of a gin-style API to zip.
|
migrate-from-gin example — mechanical port of a gin-style API to zip. |
|
module-routes
command
module-routes example — mount HIP-0105 extension modules as routes.
|
module-routes example — mount HIP-0105 extension modules as routes. |
|
plugin/billing
command
The plugin.
|
The plugin. |
|
plugin/host
command
The host.
|
The host. |
|
sse-streaming
command
sse-streaming example — Server-Sent Events via c.SendStreamWriter.
|
sse-streaming example — Server-Sent Events via c.SendStreamWriter. |
|
subsystem-mount
command
subsystem-mount example — HIP-0106 Mount(app, deps) idiom.
|
subsystem-mount example — HIP-0106 Mount(app, deps) idiom. |
|
websocket
command
websocket example — chat-style echo server.
|
websocket example — chat-style echo server. |
|
zap-typed
command
zap-typed example — typed handler with auto-generated OpenAPI spec.
|
zap-typed example — typed handler with auto-generated OpenAPI spec. |
|
internal
|
|
|
jsonenc
Package jsonenc is zip's single JSON entry point.
|
Package jsonenc is zip's single JSON entry point. |
|
jsontag
Package jsontag resolves a struct field's name on the wire.
|
Package jsontag resolves a struct field's name on the wire. |
|
runtime
Package runtime defines the minimal Loader / Module interface zip consumes from any HIP-0105 extension runtime implementation (hanzoai/base/plugins/extruntime is the reference).
|
Package runtime defines the minimal Loader / Module interface zip consumes from any HIP-0105 extension runtime implementation (hanzoai/base/plugins/extruntime is the reference). |
|
testplugin
command
Command testplugin is a minimal zip plugin used by the reload tests.
|
Command testplugin is a minimal zip plugin used by the reload tests. |
|
zipdoc
Package zipdoc extracts the documentation a service has already written — the doc comments on its typed handlers and on the fields of their In/Out types — and emits the zip.Describe calls that carry it into the OpenAPI spec and the MCP tool list at run time.
|
Package zipdoc extracts the documentation a service has already written — the doc comments on its typed handlers and on the fields of their In/Out types — and emits the zip.Describe calls that carry it into the OpenAPI spec and the MCP tool list at run time. |
|
Package middleware ships zip's canonical generic middleware stack.
|
Package middleware ships zip's canonical generic middleware stack. |
|
esbuild.go wraps esbuild's pure-Go API (no CGO) so zip can compile TS / modern-JS handler source down to ES5 that the embedded goja VM executes.
|
esbuild.go wraps esbuild's pure-Go API (no CGO) so zip can compile TS / modern-JS handler source down to ES5 that the embedded goja VM executes. |
|
Package wsx provides Fiber-v3-compatible WebSocket support via fasthttp/websocket.
|
Package wsx provides Fiber-v3-compatible WebSocket support via fasthttp/websocket. |
|
Package zaprpc is an OPTIONAL named-service RPC dispatch helper for zip apps that want a Cap'n-Proto/gRPC-style service registry (name → method → handler) rather than plain REST routes.
|
Package zaprpc is an OPTIONAL named-service RPC dispatch helper for zip apps that want a Cap'n-Proto/gRPC-style service registry (name → method → handler) rather than plain REST routes. |