Files
base/plugins/extruntime/native.go
T
zeekay 33793e3b16 feat(base): extension runtime interface + native impl
Foundation for pluggable extension runtimes. One Runtime interface
that wazero, v8go, goja, and native-Go all satisfy. Extensions
declare their preferred runtime in extension.json; the loader
dispatches.

This sits alongside plugins/jsvm — goja-via-the-existing-jsvm-path
remains the default for .base.js hooks. The new path is for users
who want hard-sandboxed WASM extensions or full V8 perf.

Files:
- plugins/extruntime/extruntime.go — Runtime + Module interfaces, Manifest
- plugins/extruntime/manifest.go — JSON manifest loader (stdlib only)
- plugins/extruntime/native.go — reference impl, registers Go funcs

Concrete runtimes land in sibling commits:
- plugins/wasmvm — wazero (pure Go, hard sandbox)
- plugins/v8vm — v8go (cgo, -tags v8vm)
- plugins/gojavm — goja adapter (no new deps)
- plugins/extbench — fixtures + benchmarks across all 5
2026-05-18 17:40:50 -07:00

92 lines
2.4 KiB
Go

package extruntime
import (
"context"
"fmt"
"sync"
)
// NativeFunc is the signature every native extension function must satisfy.
type NativeFunc func(ctx context.Context, payload []byte) ([]byte, error)
// 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.
var NativeRegistry = struct {
mu sync.RWMutex
m map[string]NativeFunc
}{m: map[string]NativeFunc{}}
// RegisterNative adds a native function to the registry. Safe to call
// from init() in extension packages.
func RegisterNative(ext, fn string, f NativeFunc) {
NativeRegistry.mu.Lock()
defer NativeRegistry.mu.Unlock()
NativeRegistry.m[ext+":"+fn] = f
}
// NewNative returns a native Go runtime. Code is linked into the binary
// at compile time; the runtime just looks functions up.
func NewNative() Runtime { return &nativeRuntime{} }
type nativeRuntime struct{}
func (*nativeRuntime) Name() string { return "native" }
func (*nativeRuntime) Capabilities() Capabilities {
return Capabilities{
AcceptsLanguages: []string{"go"},
HardSandbox: false,
Cgo: false,
SupportsAbort: false,
}
}
func (*nativeRuntime) Load(ctx context.Context, dir string) (Module, error) {
m, err := LoadManifest(dir)
if err != nil {
return nil, err
}
if m.Runtime != "native" {
return nil, fmt.Errorf("%w: native runtime cannot load %q runtime", ErrUnsupported, m.Runtime)
}
return &nativeModule{name: m.Name, exports: m.Exports}, nil
}
func (*nativeRuntime) Close() error { return nil }
type nativeModule struct {
name string
exports []string
closed bool
mu sync.Mutex
}
func (m *nativeModule) Name() string { return m.name }
func (m *nativeModule) Runtime() string { return "native" }
func (m *nativeModule) Exports() []string { return m.exports }
func (m *nativeModule) Invoke(ctx context.Context, fn string, payload []byte) ([]byte, error) {
m.mu.Lock()
if m.closed {
m.mu.Unlock()
return nil, ErrClosed
}
m.mu.Unlock()
NativeRegistry.mu.RLock()
f, ok := NativeRegistry.m[m.name+":"+fn]
NativeRegistry.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("%w: %s:%s", ErrUnknownFn, m.name, fn)
}
return f(ctx, payload)
}
func (m *nativeModule) Close() error {
m.mu.Lock()
m.closed = true
m.mu.Unlock()
return nil
}