mirror of
https://github.com/hanzoai/plugin.git
synced 2026-10-30 18:23:48 +00:00
The contract was written down twice — zip declares Module and Loader, and the loader beside base's runners declared its own copy. Structural typing was the only thing keeping them equal, which is a property nobody can see and a compiler will not check. This implements zip's. The loader, the manifest and the runners live here so cloud can stop carrying a second goja runtime of its own — a shim, a bundler and a third registry that answered the same question this one does. native, goja, wazero and pyvm. wazero is the reason this is not a list of four languages: anything compiled to WebAssembly ahead of time arrives without a new runner, and Rust walks through that door rather than getting one built for it. pyvm is real CPython through cgo with PEP 684 sub-interpreters, behind a build tag, because embedding the language beats reimplementing a subset of it. Its one dependency on base was a single call for an unguessable string, which crypto/rand says directly. Co-authored-by: Hanzo Dev <[email protected]>
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
package extruntime
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
// A function is source the host keeps and runs on demand: a JSON payload in, a
|
|
// JSON result out, and one channel back to the host for the duration of the
|
|
// call. That is the whole contract, which is why it is stated here rather than
|
|
// in any one language backend — a second language is a [Register] rather than a
|
|
// second design.
|
|
//
|
|
// It sits next to [Runtime]/[Module], which answer a different question. A
|
|
// Module is an extension package on disk, loaded once and invoked many times. A
|
|
// function is a row, handed to the Runner as source at the moment it is called,
|
|
// so nothing about it is resident between calls and nothing about it is shared
|
|
// with the next one.
|
|
|
|
// Fn is one host capability: JSON in, JSON out — the same bytes wire [Module.Invoke]
|
|
// uses, so a runtime binds every capability the same way and reads none of them.
|
|
type Fn func(arg []byte) ([]byte, error)
|
|
|
|
// Host is what a running function may ask of its host, keyed by the name the
|
|
// guest reaches it under.
|
|
//
|
|
// What a function can do is decided by whoever builds the Host and never by the
|
|
// runtime, so the same runtime serves a function that may read one collection
|
|
// and a function that may do nothing at all.
|
|
type Host map[string]Fn
|
|
|
|
// Names returns the host's capability names, sorted, so a runtime that has to
|
|
// render them into guest source renders the same source twice.
|
|
func (h Host) Names() []string {
|
|
names := make([]string, 0, len(h))
|
|
for name := range h {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
return names
|
|
}
|
|
|
|
// Runner runs one function call: src is the function's source, payload is its
|
|
// JSON argument, and host is the only channel back for the duration of the call.
|
|
//
|
|
// ctx bounds the wall clock. A Runner must stop a call whose ctx is done and
|
|
// report ctx.Err(), because a function that runs forever is one that the process
|
|
// serves nobody else during.
|
|
type Runner func(ctx context.Context, src string, payload []byte, host Host) ([]byte, error)
|
|
|
|
var runners = struct {
|
|
mu sync.RWMutex
|
|
m map[string]Runner
|
|
}{m: map[string]Runner{}}
|
|
|
|
// Register adds the Runner for a language ("js"). Safe to call from an init, and
|
|
// the last registration for a language wins.
|
|
func Register(lang string, r Runner) {
|
|
runners.mu.Lock()
|
|
defer runners.mu.Unlock()
|
|
runners.m[lang] = r
|
|
}
|
|
|
|
// Lookup returns the Runner registered for lang, or nil when a deployment links
|
|
// no backend for it.
|
|
func Lookup(lang string) Runner {
|
|
runners.mu.RLock()
|
|
defer runners.mu.RUnlock()
|
|
return runners.m[lang]
|
|
}
|