Documentation
¶
Overview ¶
Package extruntime defines the pluggable extension runtime interface used by Base's extension subsystem. Each runtime (goja, wazero, v8go, native Go) satisfies this interface so an extension declares its preferred runtime in its manifest and Base loads it without caring how the code executes.
This sits ALONGSIDE the existing plugins/jsvm goja runtime — it doesn't replace it. Goja remains the default for .base.js / .base.ts hook files. The other runtimes are opt-in via an `extension.json` manifest:
# hz_hooks/validate-email/extension.json
{
"name": "validate-email",
"version": "0.1.0",
"runtime": "wazero",
"module": "validate.wasm",
"exports": ["onCreate", "onUpdate"]
}
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNoManifest = errors.New("extruntime: extension.toml not found in directory") ErrBadManifest = errors.New("extruntime: extension.toml is invalid") ErrUnknownFn = errors.New("extruntime: function not found in module") ErrClosed = errors.New("extruntime: module is closed") ErrUnsupported = errors.New("extruntime: runtime does not support this operation") )
Errors returned at the boundary. Wrappers should keep using errors.Is.
var NativeRegistry = struct { mu sync.RWMutex m map[string]NativeFunc }{/* contains filtered or unexported fields */}
NativeRegistry is a process-wide registry of compiled-in native Go extension functions, keyed by "<extension-name>:<fn-name>". The native runtime resolves Invoke() against this map. Tests and benchmarks register fixtures here; production code links extensions in at build time the same way.
Functions ¶
func RegisterNative ¶
func RegisterNative(ext, fn string, f NativeFunc)
RegisterNative adds a native function to the registry. Safe to call from init() in extension packages.
Types ¶
type Capabilities ¶
type Capabilities struct {
// AcceptsLanguages lists the source languages this runtime can run.
// native: ["go"]
// goja: ["js"]
// wazero: ["wasm"] (compile any-lang-to-wasm ahead of time)
// v8go: ["js"]
AcceptsLanguages []string
// HardSandbox is true if the runtime enforces a memory-isolated
// sandbox (wazero linear memory, v8go isolate). goja is false
// (shares Go heap with host); native is N/A (it IS the host).
HardSandbox bool
// Cgo indicates the runtime requires cgo to build. Affects binary
// distribution and cross-compilation.
Cgo bool
// SupportsAbort is true if a context cancellation can interrupt a
// running invocation without cooperative checks in the guest code.
SupportsAbort bool
}
Capabilities describes what a runtime supports — used so callers can decide whether to load a given extension or skip it (e.g. v8go-only builds shouldn't try to load AssemblyScript wasm).
type Loader ¶
type Loader struct {
// contains filtered or unexported fields
}
Loader resolves extension manifests against a runtime registry and loads each extension with the runtime whose Name() matches the manifest's `runtime` field.
Construct one Loader per process, register every runtime you have linked in, and call LoadDir() against your extensions directory. Failures on individual extensions are logged but do not abort the load — callers get back the modules that did load.
Loader is safe to call from a single goroutine. Concurrent LoadDir calls are not supported (no real use case — boot once).
func NewLoader ¶
NewLoader builds a Loader keyed by each runtime's Name(). The last runtime registered for a given name wins; passing two runtimes with the same Name() is a caller bug.
func (*Loader) Close ¶
Close closes every registered runtime. It returns the first error encountered but tries every runtime regardless. Callers should Close individual modules before Close-ing the loader.
func (*Loader) LoadDir ¶
LoadDir scans dir for subdirectories containing an extension.json manifest and loads each one with the matching runtime. The returned map is keyed by manifest.Name (NOT by directory name) — two extensions with the same name in the same dir is a manifest bug and the second one logged-and-skipped.
Missing dir is not an error: returns an empty map. Individual extension failures (bad manifest, unknown runtime, runtime.Load failure) are logged and skipped.
type Manifest ¶
type Manifest struct {
Name string `json:"name"`
Version string `json:"version"`
Runtime string `json:"runtime"`
Module string `json:"module"`
Exports []string `json:"exports"`
}
Manifest is the parsed extension.json contents.
func LoadManifest ¶
LoadManifest reads extension.json from dir and returns the parsed manifest. JSON over TOML — base has zero TOML deps and the manifest is small enough that the JSON ergonomics tax is irrelevant.
type Module ¶
type Module interface {
// Name returns the extension name from its manifest.
Name() string
// Runtime returns the runtime that loaded this module.
Runtime() string
// Exports returns the list of function names this module declares
// in its manifest. Invoke may still be called with any name — the
// runtime decides whether to error or no-op.
Exports() []string
// Invoke executes the named function with a JSON-encoded payload
// and returns a JSON-encoded result. Payloads are bytes so the
// runtime layer doesn't impose a wire format on the host code —
// callers serialize once and let every runtime see the same bytes.
//
// Context cancellation must abort the running invocation; long-
// running extension code that doesn't respect ctx is the
// extension author's bug, not the runtime's. wazero and v8go can
// hard-abort; goja and native rely on cooperative ctx checking.
Invoke(ctx context.Context, fn string, payload []byte) ([]byte, error)
// Close releases the module's resources (compiled wasm, isolate,
// goja runtime, etc.). After Close, Invoke must error.
Close() error
}
Module is one loaded extension. It exposes named functions.
type NativeFunc ¶
NativeFunc is the signature every native extension function must satisfy.
type Runtime ¶
type Runtime interface {
// Name returns the runtime identifier ("native" | "goja" | "wazero" | "v8go").
Name() string
// Capabilities returns metadata about what this runtime can do.
Capabilities() Capabilities
// Load reads an extension package from disk and returns a Module
// instance. The path is the directory containing the extension.json
// manifest. Implementations should pre-compile / pre-instantiate
// here so the per-invocation Invoke cost is minimized.
Load(ctx context.Context, dir string) (Module, error)
// Close releases any runtime-level resources (compilation cache, etc.).
// Modules created by this runtime should already be Close()d by callers.
Close() error
}
Runtime is implemented by every backing engine.