bi test
This commit is contained in:
201
builtin.go
Normal file
201
builtin.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type BuiltinFunction any
|
||||
|
||||
type Builtin struct {
|
||||
Name string
|
||||
Function any
|
||||
Definition string
|
||||
}
|
||||
|
||||
var (
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
)
|
||||
|
||||
func RegisterBuiltin[T any](name string, fn T) {
|
||||
builtinRegistry[name] = createBuiltin(name, fn)
|
||||
}
|
||||
|
||||
func createBuiltin(name string, fn any) Builtin {
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
fnType := fnValue.Type()
|
||||
|
||||
tsDef := generateTypeScriptDefinition(name, fnType)
|
||||
|
||||
return Builtin{
|
||||
Name: name,
|
||||
Function: fn,
|
||||
Definition: tsDef,
|
||||
}
|
||||
}
|
||||
|
||||
func generateTypeScriptDefinition(name string, fnType reflect.Type) string {
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return ""
|
||||
}
|
||||
|
||||
var params []string
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
params = append(params, fmt.Sprintf("arg%d: %s", i, goTypeToTSType(fnType.In(i))))
|
||||
}
|
||||
|
||||
returnSignature := "void"
|
||||
if fnType.NumOut() > 0 {
|
||||
returnType := fnType.Out(0)
|
||||
returnSignature = goTypeToTSType(returnType)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("declare function %s(%s): %s;", name, strings.Join(params, ", "), returnSignature)
|
||||
}
|
||||
|
||||
func goTypeToTSType(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return "number"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return "number"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "number"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
if t.String() == "goja.Value" {
|
||||
return "any"
|
||||
}
|
||||
return "any"
|
||||
case reflect.Slice:
|
||||
return fmt.Sprintf("%s[]", goTypeToTSType(t.Elem()))
|
||||
case reflect.Map:
|
||||
return "Record<string, any>"
|
||||
case reflect.Struct:
|
||||
return "any"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
|
||||
func GetBuiltinsDeclarations() string {
|
||||
registryMutex.RLock()
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
var decls []string
|
||||
for _, builtin := range builtinRegistry {
|
||||
decls = append(decls, builtin.Definition)
|
||||
}
|
||||
return strings.Join(decls, "\n")
|
||||
}
|
||||
|
||||
func RegisterBuiltins(vm *goja.Runtime) {
|
||||
RegisterFetchBuiltin(vm)
|
||||
|
||||
registryMutex.RLock()
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
for name, builtin := range builtinRegistry {
|
||||
if builtin.Function != nil {
|
||||
_ = vm.Set(name, builtin.Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FetchResult struct {
|
||||
OK bool
|
||||
Status int
|
||||
Body string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
for key, values := range resp.Header {
|
||||
if len(values) > 0 {
|
||||
headers[key] = values[0]
|
||||
headers[strings.ToLower(key)] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
return &FetchResult{
|
||||
OK: resp.StatusCode >= 200 && resp.StatusCode < 300,
|
||||
Status: resp.StatusCode,
|
||||
Body: string(body),
|
||||
Headers: headers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RegisterFetchBuiltin(vm *goja.Runtime) {
|
||||
_ = vm.Set("fetch", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
panic("fetch requires at least 1 argument")
|
||||
}
|
||||
|
||||
url := call.Arguments[0].String()
|
||||
|
||||
result, err := Fetch(url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultObj := vm.NewObject()
|
||||
_ = resultObj.Set("ok", result.OK)
|
||||
_ = resultObj.Set("status", result.Status)
|
||||
|
||||
body := result.Body
|
||||
_ = resultObj.Set("text", func() string {
|
||||
return body
|
||||
})
|
||||
|
||||
headersObj := vm.NewObject()
|
||||
headers := result.Headers
|
||||
_ = headersObj.Set("get", func(c goja.FunctionCall) goja.Value {
|
||||
if len(c.Arguments) < 1 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
key := c.Arguments[0].String()
|
||||
return vm.ToValue(headers[key])
|
||||
})
|
||||
_ = resultObj.Set("headers", headersObj)
|
||||
|
||||
return resultObj
|
||||
})
|
||||
|
||||
builtinRegistry["fetch"] = Builtin{
|
||||
Name: "fetch",
|
||||
Function: nil,
|
||||
Definition: "declare function fetch(url: string, options?: any): PromiseLike<any>;",
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
||||
Reference in New Issue
Block a user