modernc
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"modernc.org/quickjs"
|
||||
)
|
||||
|
||||
type TestArgs struct {
|
||||
@@ -36,15 +36,18 @@ func TestAsyncBuiltinResolution(t *testing.T) {
|
||||
return "test-result", nil
|
||||
})
|
||||
|
||||
vm := goja.New()
|
||||
vm, err := quickjs.NewVM()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = vm.Close()
|
||||
}()
|
||||
vm.SetCanBlock(true)
|
||||
|
||||
RegisterBuiltins(vm)
|
||||
|
||||
result, err := vm.RunString(`resolveTest({field1: "hello"})`)
|
||||
result, err := vm.Eval(`resolveTest({field1: "hello"})`, quickjs.EvalGlobal)
|
||||
require.NoError(t, err)
|
||||
|
||||
promise, ok := result.Export().(*goja.Promise)
|
||||
require.True(t, ok, "should return a Promise")
|
||||
assert.NotNil(t, promise)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestAsyncBuiltinRejection(t *testing.T) {
|
||||
@@ -52,15 +55,18 @@ func TestAsyncBuiltinRejection(t *testing.T) {
|
||||
return "", assert.AnError
|
||||
})
|
||||
|
||||
vm := goja.New()
|
||||
vm, err := quickjs.NewVM()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = vm.Close()
|
||||
}()
|
||||
vm.SetCanBlock(true)
|
||||
|
||||
RegisterBuiltins(vm)
|
||||
|
||||
result, err := vm.RunString(`rejectTest({field1: "hello"})`)
|
||||
result, err := vm.Eval(`rejectTest({field1: "hello"})`, quickjs.EvalGlobal)
|
||||
require.NoError(t, err)
|
||||
|
||||
promise, ok := result.Export().(*goja.Promise)
|
||||
require.True(t, ok, "should return a Promise")
|
||||
assert.NotNil(t, promise)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestNonPromise(t *testing.T) {
|
||||
@@ -68,10 +74,22 @@ func TestNonPromise(t *testing.T) {
|
||||
return "sync-result", nil
|
||||
})
|
||||
|
||||
vm := goja.New()
|
||||
vm, err := quickjs.NewVM()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = vm.Close()
|
||||
}()
|
||||
vm.SetCanBlock(true)
|
||||
|
||||
RegisterBuiltins(vm)
|
||||
|
||||
result, err := vm.RunString(`nonPromiseTest({field1: "hello"})`)
|
||||
result, err := vm.Eval(`nonPromiseTest({field1: "hello"})`, quickjs.EvalGlobal)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "sync-result", result.Export())
|
||||
|
||||
if obj, ok := result.(*quickjs.Object); ok {
|
||||
var arr []any
|
||||
if err := obj.Into(&arr); err == nil && len(arr) > 0 {
|
||||
assert.Equal(t, "sync-result", arr[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect.Type) (any, error) {
|
||||
if goja.IsNull(jsValue) {
|
||||
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
||||
}
|
||||
|
||||
if goja.IsUndefined(jsValue) {
|
||||
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
||||
}
|
||||
|
||||
switch targetType.Kind() {
|
||||
case reflect.String:
|
||||
return jsValue.String(), nil
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
n, ok := jsValue.Export().(int64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected int, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
n, ok := jsValue.Export().(int64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected uint, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(uint(n)).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
n, ok := jsValue.Export().(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected float, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Bool:
|
||||
return jsValue.ToBoolean(), nil
|
||||
|
||||
case reflect.Interface:
|
||||
return jsValue.Export(), nil
|
||||
|
||||
case reflect.Map:
|
||||
if goja.IsUndefined(jsValue) || goja.IsNull(jsValue) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if targetType.Key().Kind() == reflect.String {
|
||||
obj := jsValue.ToObject(vm)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("not an object")
|
||||
}
|
||||
|
||||
if targetType.Elem().Kind() == reflect.Interface {
|
||||
result := make(map[string]any)
|
||||
for _, key := range obj.Keys() {
|
||||
result[key] = obj.Get(key).Export()
|
||||
}
|
||||
return result, nil
|
||||
} else if targetType.Elem().Kind() == reflect.String {
|
||||
result := make(map[string]string)
|
||||
for _, key := range obj.Keys() {
|
||||
v := obj.Get(key)
|
||||
result[key] = v.String()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported map type: %v", targetType)
|
||||
|
||||
case reflect.Struct:
|
||||
obj := jsValue.ToObject(vm)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("not an object")
|
||||
}
|
||||
|
||||
result := reflect.New(targetType).Elem()
|
||||
for i := 0; i < targetType.NumField(); i++ {
|
||||
field := targetType.Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
|
||||
jsField := obj.Get(fieldName)
|
||||
|
||||
var err error
|
||||
var converted any
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = nil
|
||||
converted = nil
|
||||
}
|
||||
}()
|
||||
converted, err = convertJSValueToGo(vm, jsField, field.Type)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field %s: %v", fieldName, err)
|
||||
}
|
||||
|
||||
if converted == nil {
|
||||
if field.Type.Kind() == reflect.Pointer || field.Type.Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
result.Field(i).Set(reflect.ValueOf(converted))
|
||||
}
|
||||
}
|
||||
return result.Interface(), nil
|
||||
|
||||
case reflect.Pointer:
|
||||
if goja.IsNull(jsValue) || goja.IsUndefined(jsValue) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
elemType := targetType.Elem()
|
||||
converted, err := convertJSValueToGo(vm, jsValue, elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ptr := reflect.New(elemType)
|
||||
ptr.Elem().Set(reflect.ValueOf(converted))
|
||||
return ptr.Interface(), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", targetType)
|
||||
}
|
||||
}
|
||||
|
||||
func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
value := goValue.Interface()
|
||||
|
||||
switch v := value.(type) {
|
||||
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
|
||||
return vm.ToValue(v)
|
||||
|
||||
case error:
|
||||
return vm.ToValue(v.Error())
|
||||
|
||||
case map[string]string:
|
||||
obj := vm.NewObject()
|
||||
for key, val := range v {
|
||||
_ = obj.Set(key, val)
|
||||
}
|
||||
return obj
|
||||
|
||||
case map[string]any:
|
||||
obj := vm.NewObject()
|
||||
for key, val := range v {
|
||||
_ = obj.Set(key, convertGoValueToJS(vm, reflect.ValueOf(val)))
|
||||
}
|
||||
return obj
|
||||
|
||||
case []any:
|
||||
arr := make([]goja.Value, len(v))
|
||||
for i, item := range v {
|
||||
arr[i] = convertGoValueToJS(vm, reflect.ValueOf(item))
|
||||
}
|
||||
return vm.ToValue(arr)
|
||||
|
||||
default:
|
||||
if goValue.Kind() == reflect.Pointer {
|
||||
if goValue.IsNil() {
|
||||
return goja.Null()
|
||||
}
|
||||
return convertGoValueToJS(vm, goValue.Elem())
|
||||
}
|
||||
|
||||
if goValue.Kind() == reflect.Struct {
|
||||
obj := vm.NewObject()
|
||||
for i := 0; i < goValue.NumField(); i++ {
|
||||
field := goValue.Type().Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
_ = obj.Set(fieldName, convertGoValueToJS(vm, goValue.Field(i)))
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
return vm.ToValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" && jsonTag != "-" {
|
||||
name, _, _ := strings.Cut(jsonTag, ",")
|
||||
return name
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"modernc.org/quickjs"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -80,11 +80,14 @@ func RegisterAsyncBuiltin[T Args, R any](name string, fn Func[T, R]) {
|
||||
registerBuiltin(name, true, fn)
|
||||
}
|
||||
|
||||
func RegisterBuiltins(vm *goja.Runtime) {
|
||||
func RegisterBuiltins(vm *quickjs.VM) {
|
||||
registryMutex.RLock()
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
for name, builtin := range builtinRegistry {
|
||||
_ = vm.Set(name, builtin.Function(vm))
|
||||
err := vm.RegisterFunc(name, builtin.Function, false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to register builtin %s: %v", name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@ package builtin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type Builtin struct {
|
||||
Name string
|
||||
Function func(*goja.Runtime) func(goja.FunctionCall) goja.Value
|
||||
Function interface{}
|
||||
Definition string
|
||||
Types []string
|
||||
ParamTypes map[string]bool
|
||||
|
||||
@@ -6,6 +6,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" && jsonTag != "-" {
|
||||
name, _, _ := strings.Cut(jsonTag, ",")
|
||||
return name
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func generateTypeScriptDefinition(name string, argsType reflect.Type, fnType reflect.Type, isPromise bool, paramTypes map[string]bool) string {
|
||||
if argsType.Kind() != reflect.Struct {
|
||||
return ""
|
||||
|
||||
@@ -2,71 +2,80 @@ package builtin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"modernc.org/quickjs"
|
||||
)
|
||||
|
||||
func createWrapper[T Args, R any](fn Func[T, R], isAsync bool) func(*goja.Runtime) func(goja.FunctionCall) goja.Value {
|
||||
return func(vm *goja.Runtime) func(goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
var args T
|
||||
argsValue := reflect.ValueOf(&args).Elem()
|
||||
func createWrapper[T Args, R any](fn Func[T, R], isAsync bool) interface{} {
|
||||
if !isAsync {
|
||||
return createSyncWrapper[T, R](fn)
|
||||
}
|
||||
return createAsyncWrapper[T, R](fn)
|
||||
}
|
||||
|
||||
for i := 0; i < argsValue.NumField() && i < len(call.Arguments); i++ {
|
||||
jsArg := call.Arguments[i]
|
||||
field := argsValue.Field(i)
|
||||
|
||||
if goja.IsUndefined(jsArg) || goja.IsNull(jsArg) {
|
||||
if field.Kind() == reflect.Pointer {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
converted, err := convertJSValueToGo(vm, jsArg, field.Type())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("argument %d (%s): %v", i, getFieldName(argsValue.Type().Field(i)), err))
|
||||
}
|
||||
|
||||
if converted != nil {
|
||||
field.Set(reflect.ValueOf(converted))
|
||||
}
|
||||
}
|
||||
|
||||
if err := args.Validate(); err != nil {
|
||||
panic(fmt.Sprintf("argument validation failed: %v", err))
|
||||
}
|
||||
|
||||
if isAsync {
|
||||
return createAsyncPromise(vm, fn, args)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := fn(ctx, args)
|
||||
func createSyncWrapper[T Args, R any](fn Func[T, R]) interface{} {
|
||||
return func(rawArgs any) (R, error) {
|
||||
var zero R
|
||||
var args T
|
||||
|
||||
obj, ok := rawArgs.(*quickjs.Object)
|
||||
if ok {
|
||||
jsonData, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return zero, fmt.Errorf("failed to marshal args: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &args); err != nil {
|
||||
return zero, fmt.Errorf("failed to unmarshal args: %w", err)
|
||||
}
|
||||
} else if rawArgs != nil && rawArgs != quickjs.UndefinedValue {
|
||||
jsonData, err := json.Marshal(rawArgs)
|
||||
if err != nil {
|
||||
return zero, fmt.Errorf("failed to marshal args: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &args); err != nil {
|
||||
return zero, fmt.Errorf("failed to unmarshal args: %w", err)
|
||||
}
|
||||
|
||||
return convertGoValueToJS(vm, reflect.ValueOf(result))
|
||||
}
|
||||
|
||||
if err := args.Validate(); err != nil {
|
||||
return zero, fmt.Errorf("argument validation failed: %w", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
return fn(ctx, args)
|
||||
}
|
||||
}
|
||||
|
||||
func createAsyncPromise[T Args, R any](vm *goja.Runtime, fn Func[T, R], args T) goja.Value {
|
||||
promise, resolve, reject := vm.NewPromise()
|
||||
func createAsyncWrapper[T Args, R any](fn Func[T, R]) interface{} {
|
||||
return func(rawArgs any) (any, error) {
|
||||
var args T
|
||||
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
result, err := fn(ctx, args)
|
||||
|
||||
if err != nil {
|
||||
_ = reject(vm.ToValue(err.Error()))
|
||||
} else {
|
||||
_ = resolve(convertGoValueToJS(vm, reflect.ValueOf(result)))
|
||||
obj, ok := rawArgs.(*quickjs.Object)
|
||||
if ok {
|
||||
jsonData, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal args: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &args); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal args: %w", err)
|
||||
}
|
||||
} else if rawArgs != nil && rawArgs != quickjs.UndefinedValue {
|
||||
jsonData, err := json.Marshal(rawArgs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal args: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &args); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal args: %w", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return vm.ToValue(promise)
|
||||
if err := args.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("argument validation failed: %w", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
return fn(ctx, args)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user