wasmhost

package
v0.7.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package wasmhost runs an app's HTTP services (typically Connect handlers) as WebAssembly inside a browser Web Worker, answering the same generated clients a server answers.

A page pushes files in by name before it asks anything, and the handler reads them through one fs.FS (Root) whose top-level directories are the mounts. The page side lives in tsappkit (@panyam/tsappkit/wasmhost), which starts the worker and hands the Connect transport a fetch that posts to it.

The rules it encodes, each with a failure behind it (goapplib issue 32):

  • Never block inside a js.FuncOf callback. Every export returns a Promise and does its work on a goroutine, because a callback that waits on something the JS event loop must deliver (a fetch, a timer, a Promise) deadlocks the worker.
  • Push the files in before the request, never pull them during it. fs.FS is synchronous and every browser source of bytes is asynchronous, so the files go into memory first. State a service builds for itself is different: it reads and writes a Cache (BrowserCache in the browser, over the Origin Private File System), whose calls wait on the browser and so are made from a handler's goroutine, which is allowed to wait, never from a js.FuncOf callback.
  • Speak the app's existing wire protocol, so its generated clients don't know whether a server or the worker answered.

Host holds the platform-neutral part (mount table, handler, in-process requests) and builds and tests natively. Serve, ServeRebuild and Host.Export, which talk to JavaScript, are js && wasm only.

Index

Constants

This section is empty.

Variables

View Source
var ErrMiss = errors.New("wasmhost: not in the cache")

ErrMiss is what Get returns for a key the cache has nothing under.

View Source
var ErrNoCache = errors.New("wasmhost: no cache in this browser")

ErrNoCache is what BrowserCache's methods return when the browser has no storage to offer the worker (no Origin Private File System, or a worker started without one). A service treats it as a miss and rebuilds.

View Source
var ErrNoHandler = errors.New("wasmhost: no handler")

ErrNoHandler is what Do returns before Handle or Rebuild has given the host a handler.

Functions

func CacheKey

func CacheKey(parts ...[]byte) string

CacheKey is a cache key for inputs: the hex SHA-256 of the parts, each prefixed with its length, so ("ab", "c") and ("a", "bc") get different keys. Put a version string first, so a service that changes what it stores stops finding blobs an older build wrote.

Types

type Cache

type Cache interface {
	// Get returns a copy of the blob under key, or ErrMiss. A key that isn't one CacheKey could
	// make is an error rather than a miss.
	Get(ctx context.Context, key string) ([]byte, error)
	// Put stores b under key, replacing what was there. A reader never sees half a blob: it gets
	// the old one, the new one, or ErrMiss. The cache keeps its own copy of b.
	Put(ctx context.Context, key string, b []byte) error
}

Cache keeps state a service builds from its inputs and would rather not build again, such as a parsed design, a model or an index, so a reload or a restarted worker can skip the rebuild. Keys name the inputs (see CacheKey), not the blob, so a service computes the key from a request before it has anything cached, and the same inputs always find the same blob. In the browser it's BrowserCache, over the Origin Private File System, which outlives a reload; natively it's DirCache or MemCache.

Every worker the page starts for the same namespace sees the same BrowserCache, so a throwaway worker (tsappkit's oneShotFetch) can build a result through a large peak and Put it, and the long-lived worker Gets it without ever holding the peak.

It's a cache, not a datastore: anything in it must be rebuildable from its inputs, because a miss, ErrNoCache, a browser clearing the origin's storage, or any other error just means the service builds it again. Data a user would lose belongs in the app's server-side storage.

Get and Put may wait on the browser, so call them from a handler or another goroutine, never from inside a js.FuncOf callback (see the package doc). Implementations are safe for concurrent use.

func DirCache

func DirCache(dir string) Cache

DirCache is a Cache in a directory, one file per key, for a service running natively (on a server, or in tests). The directory is created on the first Put.

type Chunk

type Chunk struct {
	Status int
	Header http.Header
	Body   []byte
}

Chunk is part of a response, sent when the handler calls Flush (http.Flusher). The first chunk of a response carries its status and header; later ones carry only body.

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host is a mount table and the handler that serves requests over it.

A Host is in one of two modes. After Handle it serves one fixed handler, which reads the live Root and so sees each mount change on its next read. After Rebuild it calls the build function again on every Mount and Unmount, for apps whose handler snapshots the files when it is made. All methods are safe for concurrent use.

func New

func New(ns string) *Host

New returns an empty host whose JavaScript exports, once Export or Serve installs them, live on globalThis[ns].

func (*Host) Add

func (h *Host) Add(name string, files map[string][]byte) error

Add puts files into the mount name, keeping the files it already holds; a file at a path the mount already has replaces it. A missing mount is created, as Mount would. It takes ownership of the byte slices, and in rebuild mode it rebuilds once for the whole batch.

Add checks the whole batch before writing anything, so an error (an invalid path, a path that is already a directory, a file under a file) leaves the mount as it was. With a fixed handler (Handle), a request running at the same moment may see some of the batch and not the rest; in rebuild mode the new handler is built after the last file is in.

func (*Host) Do

func (h *Host) Do(ctx context.Context, req Request) (Response, error)

Do serves req in-process and returns the buffered response. An HTTP error status is a Response, not an error. The error is for no handler (ErrNoHandler), a failed rebuild, an unparseable request, or a handler panic, which is recovered so one bad request can't kill the worker.

func (*Host) DoStream

func (h *Host) DoStream(ctx context.Context, req Request, onChunk func(Chunk)) (res Response, err error)

DoStream is Do for a handler that streams: each time it calls Flush, the bytes it has written since the last Flush go to onChunk, synchronously and in order, while the handler is still running. The returned Response holds only what was written after the last Flush; if anything was flushed, its Status and Header repeat the first chunk's. A nil onChunk buffers the whole response, as Do does. onChunk is called on the handler's goroutine and must not block on it.

func (*Host) Handle

func (h *Host) Handle(handler http.Handler)

Handle makes handler the host's one fixed handler, leaving rebuild mode if it was in it.

func (*Host) Mount

func (h *Host) Mount(name string, files map[string][]byte) error

Mount replaces the mount name with files, keyed by slash-separated path. It takes ownership of the byte slices. In rebuild mode it returns the build's error, and the mount stays in place either way.

func (*Host) Namespace

func (h *Host) Namespace() string

Namespace is the global name the host's exports are installed under.

func (*Host) Rebuild

func (h *Host) Rebuild(build func(root fs.FS) (http.Handler, error)) error

Rebuild puts the host in rebuild mode: build is called now with Root and again after every Mount and Unmount, and its handler serves until the next change. If build fails, requests fail with its error until a later build succeeds. The error from this first build is returned.

func (*Host) Root

func (h *Host) Root() fs.FS

Root is the live mount table as one fs.FS: mount "docs" holding "a/b.txt" reads as "docs/a/b.txt". It is the same value on every call, so a handler can keep it.

func (*Host) Unmount

func (h *Host) Unmount(name string) error

Unmount removes the mount name, which is not an error if it is absent. In rebuild mode it returns the build's error.

type MemCache

type MemCache struct {
	// contains filtered or unexported fields
}

MemCache is a Cache in memory, for tests and for a service with nowhere to persist. It forgets everything when the process ends.

func (*MemCache) Get

func (m *MemCache) Get(_ context.Context, key string) ([]byte, error)

Get implements Cache.

func (*MemCache) Put

func (m *MemCache) Put(_ context.Context, key string, b []byte) error

Put implements Cache.

type Request

type Request struct {
	Method string
	URL    string
	Header http.Header
	Body   []byte
}

Request is one HTTP request as the worker receives it. URL is a path with an optional query ("/pkg.Service/Method?x=1"); the scheme and host the page used don't matter here.

type Response

type Response struct {
	Status int
	Header http.Header
	Body   []byte
}

Response is the handler's answer, buffered whole.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL