mirror of
https://github.com/hanzoai/wasm.git
synced 2026-10-30 15:07:30 +00:00
One home for acting on source: is it correct, and can it be built. Two interfaces because most checkers do not bundle, and a registry so that supporting another language is a row rather than a route — GET on the registry and running a checker are the same address read or run. A project names files, never a disk path. The host answers ReadFile, FileExists, DirExists, Entries and Realpath, so a checker reaches exactly what it was handed: the repository lives in s3 and the cache is the host's business. esbuild takes the whole project through onResolve and onLoad with zero preopened directories; tsgo issues ordinary WASI reads, so its mount is an fs.FS over the same callbacks, and a project with no tsconfig.json gets one that exists only in the guest's view of the tree. Diagnostics are one shape with 1-based lines and columns. esbuild counts columns from zero; that translation happens once here instead of in every caller. tsgo is a command module, so a check is an instance: compile once into an on-disk cache, instantiate many with a unique name each, es2022 with no ambient types by default and GOMEMLIMIT per run. On evo that is 273ms for one check and 541ms for eight at once, against 1.7s and 2.35GB with the default library bundle. esbuild registers as a Bundler and nothing else. It bundled `const wrong: string = add(1,2)` and exited 0, and the registry is what keeps that honest. The modules are 20 and 49 MiB and are not committed: Blob names one by path or URL and its sha256, and refuses a module with no digest. Co-authored-by: Hanzo Dev <[email protected]>
83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
|
|
package compile
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Blob names a wasm module by where to get it and what it must hash to. tsgo is
|
|
// 49 MiB and esbuild 20 MiB, so neither lives in this repository; the host
|
|
// fetches them and this type is why a fetch cannot substitute something else.
|
|
//
|
|
// Sum is required. A module with no declared digest is refused rather than
|
|
// trusted, because the one thing a compile host must never be is a way to run
|
|
// somebody else's code.
|
|
type Blob struct {
|
|
// Path is a local file. It wins when set.
|
|
Path string
|
|
// URL is fetched over http when Path is empty.
|
|
URL string
|
|
// Sum is the hex sha256 the bytes must have.
|
|
Sum string
|
|
}
|
|
|
|
// Bytes fetches the module and verifies it. A digest mismatch names both
|
|
// digests: the caller has either a corrupt download or the wrong artifact, and
|
|
// those need different fixes.
|
|
func (b Blob) Bytes(ctx context.Context) ([]byte, error) {
|
|
if b.Sum == "" {
|
|
return nil, fmt.Errorf("compile: blob has no sha256")
|
|
}
|
|
var (
|
|
raw []byte
|
|
err error
|
|
)
|
|
switch {
|
|
case b.Path != "":
|
|
raw, err = os.ReadFile(b.Path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile: read module: %w", err)
|
|
}
|
|
case b.URL != "":
|
|
raw, err = fetch(ctx, b.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("compile: blob names neither a path nor a url")
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
if got := hex.EncodeToString(sum[:]); !strings.EqualFold(got, b.Sum) {
|
|
return nil, fmt.Errorf("compile: module digest is %s, declared %s", got, b.Sum)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
func fetch(ctx context.Context, url string) ([]byte, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile: fetch module: %w", err)
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile: fetch module: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("compile: fetch module %s: %s", url, resp.Status)
|
|
}
|
|
raw, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile: fetch module %s: %w", url, err)
|
|
}
|
|
return raw, nil
|
|
}
|