509a645e32 zip: OnShutdown — LIFO lifecycle hooks for express-style composition (#4)
Add the one teardown primitive zip was missing:

    func (a *App) OnShutdown(fn func(context.Context) error)

Subsystems register their own cleanup at mount time; reverse-mount
teardown falls out for free. This is what consumers (hanzoai/cloud)
need to flatten a MountSpec{Shutdown} registry into plain
pkg.Mount(app, cfg) calls.

Semantics:
- Order: hooks run LAST — after listeners stop accepting and after
  in-flight requests drain (fiber) — in LIFO order. Draining first
  means a subsystem's teardown never races the requests still using
  it; LIFO gives reverse-mount teardown of dependencies.
- Errors: every hook runs even if an earlier one fails; all hook
  errors (plus the drain error) are aggregated via errors.Join.
- Context: ShutdownWithContext passes its ctx (values + deadline) to
  hooks; plain Shutdown uses context.Background() (process ending).
- Idempotent: a shared once-guard makes the whole Shutdown a no-op on
  the second call; hooks run at most once, across both entry points.
- Post-shutdown registration is a no-op: the hook is dropped and a
  warning logged (no panic). Keeps OnShutdown's meaning constant —
  register-for-later — instead of silently changing to run-now.
- Registration is mutex-guarded; a nil hook is ignored.

Decomplect: fold the internal `closers` stopgap onto OnShutdown.
module.go's own comment described appendCloser as a poor-man's
graceful-shutdown hook list "because fiber doesn't expose one" — which
is exactly this primitive. Module() now registers mod.Close via
OnShutdown, so there is ONE teardown mechanism. A module's Close now
runs AFTER in-flight requests drain (it previously ran before), so a
module stays live for any request still executing it — a correctness
improvement. closers.go is deleted.

Tests (shutdown_test.go): LIFO order, errors.Join aggregation,
idempotent second Shutdown, ShutdownWithContext ctx propagation,
post-shutdown drop, nil-hook, and concurrent registration under -race.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 16:13:22 -07:00

zip

Docs: zip · part of the ZAP Protocol

The ZAP-native Go web framework. Built on Fiber v3 / fasthttp, with a Sinatra-style API, typed handlers that project to OpenAPI and MCP, and ZAP as the primary transport — HTTP is a secondary view of the same routes.

zap-proto.io · Docs · fiber · Spec

ONE framework. ONE Listen verb. Routes defined once, served over every transport.

package main

import (
    "github.com/zap-proto/zip"
    "github.com/zap-proto/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(),  // gateway-minted X-Org-Id
            "user": c.User(), // gateway-minted X-User-Id
        })
    })

    _ = app.Listen(":9653", "http://:8080") // ZAP primary + HTTP extra, one verb
}

Install

go get github.com/zap-proto/zip

Module path github.com/zap-proto/zip. Requires Go 1.26+.

Features

  • Sinatra/Express idiom — app.Get(path, fn) is the primary API; handlers are func(c *zip.Ctx) error.

  • Transport is a value, not a method — one verb, app.Listen(addrs...), and the address scheme selects the transport (mirrors net.Listen):

    app.Listen(":9653")                  // ZAP (bare addr = the primary)
    app.Listen(":9653", "http://:8080")  // ZAP + HTTP in one call
    app.Listen("http://:8080")           // HTTP only
    app.Listen("quic://:443")            // any RegisterTransport'd protocol
    

    ZAP (TLS 1.3 + post-quantum) is the default; HTTP is built in; zip.RegisterTransport(scheme, fn) adds any future protocol with zero change to Listen.

  • One registry, three projections — zip.Get[In, Out](app, path, fn) registers one operation that becomes a REST route, an OpenAPI 3.1 doc (/.well-known/openapi.json, Swagger UI at /docs), and a Model Context Protocol tool at /mcp (JSON-RPC 2.0). Same schema, same handler. Because /mcp is an ordinary route, ZAP-native MCP is automatic. On by default; Config.MCP.Disabled to suppress.

  • Precedence is a property of the pattern — routing comes from the zap-proto/fiber fork: the most specific pattern wins regardless of registration order (static ≻ :param ≻ *), and ambiguous equal-specificity overlaps panic at startup instead of silently shadowing.

  • Identity built-in — c.Org() / c.User() / c.UserEmail() / c.IsAdmin() read JWT-validated X-* headers set by the gateway; handlers never parse tokens.

  • Middleware — Recover, RequestID, Logger, Timeout, MaxBody, CORS, RateLimit, Telemetry, Breaker in zip/middleware.

  • WebSocket & streaming — wsx.Upgrade(fn) over fasthttp/websocket; c.SendStreamWriter for SSE / chunked responses.

  • Extension routes — app.Module("POST /v1/eval", "wasm", "./policy") mounts a sandboxed extension (wasm / goja / pyvm / starlark / v8go / native) as a route.

  • Embedded JS/TS runtime — run an Express-shaped JS/TS handler in-process via goja (pure Go, no CGO); esbuild transpiles TS ahead of it, for incremental migration to native Go.

  • Drop-in migration — app.Mount("/legacy", h) and zip.AdaptNetHTTP(...) accept any http.Handler (chi, gin, beego, net/http).

  • Composition by Mount — a service is a set of subsystem packages exposing func Mount(app *zip.App, deps Deps) error; a standalone binary and a fused multi-subsystem binary are the same code with a different selection.

  • Stdlib JSON only — every JSON path goes through one internal helper backed by encoding/json/v2 when built with GOEXPERIMENT=jsonv2 (Go 1.25+), else encoding/json. No third-party JSON library.

Documentation

The full guide — Ctx reference, the route-precedence contract, middleware, extension-runtime mounts, and versioning — is at zap-proto.dev/docs/zip. Runnable examples live in examples/.

License

MIT — see LICENSE.

S
Description
ZAP-native web framework (gofiber v3) — one framework for every Go binary; ZAP primary, HTTP extra, MCP for free
Readme MIT
12 MiB
Languages
Go 92.2%
Rust 7.5%
ZAP 0.2%