mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
The root zip package imported zip/runtime for exactly ONE thing: the type of
Config.Loader. zip/runtime imports dop251/goja (a JS interpreter) and
evanw/esbuild/pkg/api (a JS bundler) directly, so every consumer of zip
compiled a JavaScript toolchain to name an interface — and the interface was
itself only a type ALIAS of a dependency-free leaf, internal/runtime.
Loader and Module now live where they are consumed: the zip root, next to
App.Module, which is the only thing that calls them. internal/runtime and the
zip/runtime aliases are deleted — one home, one name. zip/runtime keeps goja
and esbuild and stays the JavaScript implementation; it is simply a separate
import now, so the cost lands on the binaries that ask for JS.
There is no hot-path coupling to invert: the root only ever calls through the
interface (a.loader.LoadOne) into a loader the consumer constructs and passes
in via Config.Loader. That contract is unchanged.
Measured with `go list -deps . | wc -l` in a CLEAN module importing only zip,
outside any other module graph:
before 315 packages, 33 of them goja + esbuild
after 258 packages, 0
opt-in 314 packages, 33 (a consumer that DOES import zip/runtime)
57 packages leave and none arrive: esbuild 25, goja 8, x/text 9, regexp2 3,
sourcemap 2, pprof 1, go/ast+token+scanner, archive/zip, encoding/base32,
hash/maphash — plus the two zip packages no longer reachable.
hanzoai/cloud, which imports zip/runtime nowhere, measured against this commit
vs its parent (same zip baseline, so the delta is only this change):
cmd/cloud 402 -> 346 (JS 33 -> 0)
cloud root 603 -> 547
plugin/crm 605 -> 549
plugin/storage 716 -> 664
cmd/cloud builds, and `go build ./...` yields the byte-identical error set
before and after (3 pre-existing k8s/autoscaling failures, unrelated).
Also corrected a doc claim that was already false: Config.Loader said it "is
satisfied by *extruntime.Loader". It is not, and was not before this commit —
extruntime.LoadDir returns map[string]extruntime.Module, and Go requires
IDENTICAL result types, so an equivalent method set does not satisfy the
interface. The docs now say an adapter returning zip.Module is required.
Co-authored-by: Hanzo Dev <[email protected]>
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package zip_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
// stubLoader is a minimal zip.Loader fixture for tests.
|
|
type stubLoader struct{ rt string }
|
|
|
|
func (s stubLoader) Runtimes() []string { return []string{s.rt} }
|
|
func (s stubLoader) LoadDir(_ context.Context, _ string) (map[string]zip.Module, error) {
|
|
return map[string]zip.Module{}, nil
|
|
}
|
|
func (s stubLoader) LoadOne(_ context.Context, dir string) (zip.Module, error) {
|
|
return &stubModule{dir: dir, rt: s.rt}, nil
|
|
}
|
|
|
|
type stubModule struct {
|
|
dir string
|
|
rt string
|
|
}
|
|
|
|
func (m *stubModule) Name() string { return m.dir }
|
|
func (m *stubModule) Runtime() string { return m.rt }
|
|
func (m *stubModule) Exports() []string { return []string{"handler"} }
|
|
func (m *stubModule) Close() error { return nil }
|
|
func (m *stubModule) Invoke(_ context.Context, fn string, payload []byte) ([]byte, error) {
|
|
return json.Marshal(map[string]any{
|
|
"status": 201,
|
|
"headers": map[string]string{"X-Test": "1"},
|
|
"body": map[string]any{"fn": fn, "echo": json.RawMessage(payload)},
|
|
})
|
|
}
|
|
|
|
func TestModuleRoute(t *testing.T) {
|
|
app := zip.New(zip.Config{
|
|
DisableStartupMessage: true,
|
|
Loader: stubLoader{rt: "goja"},
|
|
AllowedRuntimes: []string{"goja"},
|
|
})
|
|
if err := app.Module("POST /v1/eval", "goja", "./fixtures/eval"); err != nil {
|
|
t.Fatalf("Module: %v", err)
|
|
}
|
|
req, _ := http.NewRequest("POST", "/v1/eval", strings.NewReader(`{"x":1}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Fiber().Test(req)
|
|
if err != nil {
|
|
t.Fatalf("Test: %v", err)
|
|
}
|
|
if resp.StatusCode != 201 {
|
|
t.Fatalf("status %d, want 201", resp.StatusCode)
|
|
}
|
|
if resp.Header.Get("X-Test") != "1" {
|
|
t.Fatalf("X-Test header missing")
|
|
}
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if !strings.Contains(string(body), `"fn":"eval"`) {
|
|
t.Fatalf("body=%s", body)
|
|
}
|
|
}
|
|
|
|
func TestModuleRouteRuntimeMismatch(t *testing.T) {
|
|
app := zip.New(zip.Config{
|
|
DisableStartupMessage: true,
|
|
Loader: stubLoader{rt: "goja"},
|
|
})
|
|
if err := app.Module("POST /v1/eval", "wazero", "./fixtures/eval"); err == nil {
|
|
t.Fatal("expected error for runtime mismatch")
|
|
}
|
|
}
|
|
|
|
func TestModuleRouteAllowedRuntimes(t *testing.T) {
|
|
app := zip.New(zip.Config{
|
|
DisableStartupMessage: true,
|
|
Loader: stubLoader{rt: "pyvm"},
|
|
AllowedRuntimes: []string{"goja", "wazero"}, // pyvm not allowed
|
|
})
|
|
if err := app.Module("POST /v1/eval", "pyvm", "./fixtures/eval"); err == nil {
|
|
t.Fatal("expected error for disallowed runtime")
|
|
}
|
|
}
|
|
|
|
func TestModuleRouteNoLoader(t *testing.T) {
|
|
app := zip.New(zip.Config{DisableStartupMessage: true})
|
|
if err := app.Module("POST /v1/eval", "goja", "./fixtures/eval"); err == nil {
|
|
t.Fatal("expected error when Loader is nil")
|
|
}
|
|
}
|