pre migrate
This commit is contained in:
@@ -5,9 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"reichard.io/poiesis/internal/builtin"
|
"reichard.io/poiesis/internal/functions"
|
||||||
"reichard.io/poiesis/internal/runtime"
|
"reichard.io/poiesis/internal/runtime"
|
||||||
_ "reichard.io/poiesis/internal/standard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -19,7 +18,7 @@ func main() {
|
|||||||
|
|
||||||
// Print Types
|
// Print Types
|
||||||
if os.Args[1] == "-print-types" {
|
if os.Args[1] == "-print-types" {
|
||||||
fmt.Println(builtin.GetBuiltinsDeclarations())
|
fmt.Println(functions.GetFunctionDeclarations())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ func main() {
|
|||||||
|
|
||||||
// Run File
|
// Run File
|
||||||
filePath := os.Args[1]
|
filePath := os.Args[1]
|
||||||
if err := rt.RunFile(filePath, os.Stdout, os.Stderr); err != nil {
|
if err := rt.RunFile(filePath); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -11,11 +11,13 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/fastschema/qjs v0.0.6 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -4,6 +4,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/evanw/esbuild v0.27.2 h1:3xBEws9y/JosfewXMM2qIyHAi+xRo8hVx475hVkJfNg=
|
github.com/evanw/esbuild v0.27.2 h1:3xBEws9y/JosfewXMM2qIyHAi+xRo8hVx475hVkJfNg=
|
||||||
github.com/evanw/esbuild v0.27.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
github.com/evanw/esbuild v0.27.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||||
|
github.com/fastschema/qjs v0.0.6 h1:C45KMmQMd21UwsUAmQHxUxiWOfzwTg1GJW0DA0AbFEE=
|
||||||
|
github.com/fastschema/qjs v0.0.6/go.mod h1:bbg36wxXnx8g0FdKIe5+nCubrQvHa7XEVWqUptjHt/A=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
@@ -18,6 +20,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||||
|
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"modernc.org/quickjs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestArgs struct {
|
type TestArgs struct {
|
||||||
@@ -19,77 +17,17 @@ func (t TestArgs) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAsyncFunction(t *testing.T) {
|
func TestAsyncFunction(t *testing.T) {
|
||||||
|
registryMutex.RLock()
|
||||||
|
defer registryMutex.RUnlock()
|
||||||
|
|
||||||
RegisterAsyncFunction("testAsync", func(_ context.Context, args TestArgs) (string, error) {
|
RegisterAsyncFunction("testAsync", func(_ context.Context, args TestArgs) (string, error) {
|
||||||
return "result: " + args.Field1, nil
|
return "result: " + args.Field1, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
registryMutex.RLock()
|
|
||||||
fn, ok := functionRegistry["testAsync"]
|
fn, ok := functionRegistry["testAsync"]
|
||||||
registryMutex.RUnlock()
|
|
||||||
|
|
||||||
require.True(t, ok, "testAsync should be registered")
|
require.True(t, ok, "testAsync should be registered")
|
||||||
assert.Contains(t, fn.Definition(), "Promise<string>", "definition should include Promise<string>")
|
assert.Contains(t, fn.Definition(), "Promise<string>", "definition should include Promise<string>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAsyncFunctionResolution(t *testing.T) {
|
// TOOD: Test Normal Function
|
||||||
RegisterAsyncFunction("resolveTest", func(_ context.Context, args TestArgs) (string, error) {
|
|
||||||
return "test-result", nil
|
|
||||||
})
|
|
||||||
|
|
||||||
vm, err := quickjs.NewVM()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = vm.Close()
|
|
||||||
}()
|
|
||||||
vm.SetCanBlock(true)
|
|
||||||
|
|
||||||
RegisterFunctions(context.Background(), vm)
|
|
||||||
|
|
||||||
result, err := vm.Eval(`resolveTest("hello")`, quickjs.EvalGlobal)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAsyncFunctionRejection(t *testing.T) {
|
|
||||||
RegisterAsyncFunction("rejectTest", func(_ context.Context, args TestArgs) (string, error) {
|
|
||||||
return "", assert.AnError
|
|
||||||
})
|
|
||||||
|
|
||||||
vm, err := quickjs.NewVM()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = vm.Close()
|
|
||||||
}()
|
|
||||||
vm.SetCanBlock(true)
|
|
||||||
|
|
||||||
RegisterFunctions(context.Background(), vm)
|
|
||||||
|
|
||||||
result, err := vm.Eval(`rejectTest({field1: "hello"})`, quickjs.EvalGlobal)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNonPromise(t *testing.T) {
|
|
||||||
RegisterFunction("nonPromiseTest", func(_ context.Context, args TestArgs) (string, error) {
|
|
||||||
return "sync-result", nil
|
|
||||||
})
|
|
||||||
|
|
||||||
vm, err := quickjs.NewVM()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = vm.Close()
|
|
||||||
}()
|
|
||||||
vm.SetCanBlock(true)
|
|
||||||
|
|
||||||
RegisterFunctions(context.Background(), vm)
|
|
||||||
|
|
||||||
result, err := vm.Eval(`nonPromiseTest({field1: "hello"})`, quickjs.EvalGlobal)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var (
|
|||||||
collector *typeCollector
|
collector *typeCollector
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerFunction[A Args, R any](name string, isAsync bool, fn RawFunc[A, R]) {
|
func registerFunction[A Args, R any](name string, isAsync bool, fn GoFunc[A, R]) {
|
||||||
registryMutex.Lock()
|
registryMutex.Lock()
|
||||||
defer registryMutex.Unlock()
|
defer registryMutex.Unlock()
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ func registerFunction[A Args, R any](name string, isAsync bool, fn RawFunc[A, R]
|
|||||||
fn: fn,
|
fn: fn,
|
||||||
types: types,
|
types: types,
|
||||||
definition: generateTypeScriptDefinition(name, tType, fnType, isAsync, paramTypes),
|
definition: generateTypeScriptDefinition(name, tType, fnType, isAsync, paramTypes),
|
||||||
|
isAsync: isAsync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +70,10 @@ func GetRegisteredFunctions() map[string]Function {
|
|||||||
return functionRegistry
|
return functionRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFunction[T Args, R any](name string, fn RawFunc[T, R]) {
|
func RegisterFunction[T Args, R any](name string, fn GoFunc[T, R]) {
|
||||||
registerFunction(name, false, fn)
|
registerFunction(name, false, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAsyncFunction[T Args, R any](name string, fn RawFunc[T, R]) {
|
func RegisterAsyncFunction[T Args, R any](name string, fn GoFunc[T, R]) {
|
||||||
registerFunction(name, true, fn)
|
registerFunction(name, true, fn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ type Function interface {
|
|||||||
Name() string
|
Name() string
|
||||||
Types() []string
|
Types() []string
|
||||||
Definition() string
|
Definition() string
|
||||||
WrapFn(context.Context) func(...any) (any, error)
|
IsAsync() bool
|
||||||
|
Call(context.Context, []any) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFunc[A Args, R any] func(context.Context, A) (R, error)
|
type GoFunc[A Args, R any] func(context.Context, A) (R, error)
|
||||||
|
|
||||||
type Args interface {
|
type Args interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
@@ -21,9 +22,10 @@ type Args interface {
|
|||||||
|
|
||||||
type functionImpl[A Args, R any] struct {
|
type functionImpl[A Args, R any] struct {
|
||||||
name string
|
name string
|
||||||
fn RawFunc[A, R]
|
fn GoFunc[A, R]
|
||||||
definition string
|
definition string
|
||||||
types []string
|
types []string
|
||||||
|
isAsync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *functionImpl[A, R]) Name() string {
|
func (b *functionImpl[A, R]) Name() string {
|
||||||
@@ -38,8 +40,19 @@ func (b *functionImpl[A, R]) Definition() string {
|
|||||||
return b.definition
|
return b.definition
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *functionImpl[A, R]) WrapFn(ctx context.Context) func(...any) (any, error) {
|
func (b *functionImpl[A, R]) IsAsync() bool {
|
||||||
return func(allArgs ...any) (any, error) {
|
return b.isAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *functionImpl[A, R]) Function() any {
|
||||||
|
return b.fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *functionImpl[A, R]) Call(ctx context.Context, allArgs []any) (any, error) {
|
||||||
|
return b.CallGeneric(ctx, allArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *functionImpl[A, R]) CallGeneric(ctx context.Context, allArgs []any) (zeroR R, err error) {
|
||||||
// Populate Arguments
|
// Populate Arguments
|
||||||
var fnArgs A
|
var fnArgs A
|
||||||
aVal := reflect.ValueOf(&fnArgs).Elem()
|
aVal := reflect.ValueOf(&fnArgs).Elem()
|
||||||
@@ -49,28 +62,16 @@ func (b *functionImpl[A, R]) WrapFn(ctx context.Context) func(...any) (any, erro
|
|||||||
field := aVal.Field(i)
|
field := aVal.Field(i)
|
||||||
|
|
||||||
if !field.CanSet() {
|
if !field.CanSet() {
|
||||||
return nil, errors.New("cannot set field")
|
return zeroR, errors.New("cannot set field")
|
||||||
}
|
}
|
||||||
|
|
||||||
argVal := reflect.ValueOf(allArgs[i])
|
argVal := reflect.ValueOf(allArgs[i])
|
||||||
if !argVal.Type().AssignableTo(field.Type()) {
|
if !argVal.Type().AssignableTo(field.Type()) {
|
||||||
return nil, errors.New("cannot assign field")
|
return zeroR, errors.New("cannot assign field")
|
||||||
}
|
}
|
||||||
|
|
||||||
field.Set(argVal)
|
field.Set(argVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate
|
return b.fn(ctx, fnArgs)
|
||||||
if err := fnArgs.Validate(); err != nil {
|
|
||||||
return nil, errors.New("cannot validate args")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Function
|
|
||||||
resp, err := b.fn(ctx, fnArgs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
17
internal/runtime/options.go
Normal file
17
internal/runtime/options.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type RuntimeOption func(*Runtime)
|
||||||
|
|
||||||
|
func WithStdout(stdout io.Writer) RuntimeOption {
|
||||||
|
return func(r *Runtime) {
|
||||||
|
r.stdout = stdout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithStderr(stderr io.Writer) RuntimeOption {
|
||||||
|
return func(r *Runtime) {
|
||||||
|
r.stderr = stderr
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/evanw/esbuild/pkg/api"
|
"github.com/evanw/esbuild/pkg/api"
|
||||||
"modernc.org/quickjs"
|
"modernc.org/quickjs"
|
||||||
|
|
||||||
"reichard.io/poiesis/internal/functions"
|
"reichard.io/poiesis/internal/functions"
|
||||||
|
_ "reichard.io/poiesis/internal/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
@@ -19,7 +22,7 @@ type Runtime struct {
|
|||||||
stderr io.Writer
|
stderr io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context) (*Runtime, error) {
|
func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) {
|
||||||
// Create VM
|
// Create VM
|
||||||
vm, err := quickjs.NewVM()
|
vm, err := quickjs.NewVM()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -33,6 +36,11 @@ func New(ctx context.Context) (*Runtime, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(r)
|
||||||
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,15 +67,89 @@ func (r *Runtime) populateGlobals() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register Custom Functions
|
// Register Custom Functions
|
||||||
for name, builtin := range functions.GetRegisteredFunctions() {
|
for name, fn := range functions.GetRegisteredFunctions() {
|
||||||
// Register Main Function
|
// Register Main Function
|
||||||
if err := r.vm.RegisterFunc(name, builtin.WrapFn(r.ctx), false); err != nil {
|
if err := r.vm.RegisterFunc(name, func(allArgs ...any) (any, error) {
|
||||||
|
return fn.Call(r.ctx, allArgs)
|
||||||
|
}, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap Exception - The QuickJS library does not allow us to throw exceptions, so we
|
// Wrap Exception - The QuickJS library does not allow us to throw exceptions, so we
|
||||||
// wrap the function with native JS to appropriately throw on error.
|
// wrap the function with native JS to appropriately throw on error.
|
||||||
if _, err := r.vm.Eval(fmt.Sprintf(`
|
wrappedFunc := wrapFunc(fn.Name(), fn.IsAsync())
|
||||||
|
if _, err := r.vm.Eval(wrappedFunc, quickjs.EvalGlobal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) RunFile(filePath string) error {
|
||||||
|
tsFileContent, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.RunCode(string(tsFileContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) RunCode(tsCode string) error {
|
||||||
|
transformedCode, err := r.transformCode(tsCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.vm.Eval(string(transformedCode), quickjs.EvalGlobal)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) transformCode(tsCode string) ([]byte, error) {
|
||||||
|
result := api.Transform(tsCode, api.TransformOptions{
|
||||||
|
Loader: api.LoaderTS,
|
||||||
|
Target: api.ES2022,
|
||||||
|
// Format: api.FormatIIFE,
|
||||||
|
Sourcemap: api.SourceMapNone,
|
||||||
|
TreeShaking: api.TreeShakingFalse,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result.Errors) > 0 {
|
||||||
|
var allErrs []string
|
||||||
|
for _, e := range result.Errors {
|
||||||
|
allErrs = append(allErrs, e.Text)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("transpilation failed: %s", strings.Join(allErrs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapFunc(funcName string, isAsync bool) string {
|
||||||
|
if isAsync {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
(function() {
|
||||||
|
const original = globalThis[%q];
|
||||||
|
|
||||||
|
globalThis[%q] = function(...args) {
|
||||||
|
console.log("calling")
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const [result, error] = original.apply(this, args);
|
||||||
|
console.log("result", result)
|
||||||
|
if (error) {
|
||||||
|
console.log("reject")
|
||||||
|
reject(new Error(error));
|
||||||
|
} else {
|
||||||
|
console.log("resolve")
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
`, funcName, funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`
|
||||||
(function() {
|
(function() {
|
||||||
const original = globalThis[%q];
|
const original = globalThis[%q];
|
||||||
|
|
||||||
@@ -79,97 +161,5 @@ func (r *Runtime) populateGlobals() error {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
`, name, name), quickjs.EvalGlobal); err != nil {
|
`, funcName, funcName)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runtime) RunFile(filePath string, stdout, stderr io.Writer) error {
|
|
||||||
r.stdout = stdout
|
|
||||||
r.stderr = stderr
|
|
||||||
|
|
||||||
content, err := r.transformFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Error: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(content.errors) > 0 {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
|
||||||
for _, err := range content.errors {
|
|
||||||
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transpilation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = r.vm.Eval(content.code, quickjs.EvalGlobal)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runtime) RunCode(tsCode string, stdout, stderr io.Writer) error {
|
|
||||||
r.stdout = stdout
|
|
||||||
r.stderr = stderr
|
|
||||||
|
|
||||||
content := r.transformCode(tsCode)
|
|
||||||
|
|
||||||
if len(content.errors) > 0 {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
|
||||||
for _, err := range content.errors {
|
|
||||||
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transpilation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := r.vm.Eval(content.code, quickjs.EvalGlobal)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type transformResult struct {
|
|
||||||
code string
|
|
||||||
errors []api.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runtime) transformFile(filePath string) (*transformResult, error) {
|
|
||||||
tsFileContent, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.transformCode(string(tsFileContent)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runtime) transformCode(tsCode string) *transformResult {
|
|
||||||
// wrappedCode := `(async () => {
|
|
||||||
// try {
|
|
||||||
// ` + tsCode + `
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error(err);
|
|
||||||
// }
|
|
||||||
// })()`
|
|
||||||
|
|
||||||
result := api.Transform(tsCode, api.TransformOptions{
|
|
||||||
Loader: api.LoaderTS,
|
|
||||||
Target: api.ES2022,
|
|
||||||
Format: api.FormatIIFE,
|
|
||||||
Sourcemap: api.SourceMapNone,
|
|
||||||
TreeShaking: api.TreeShakingFalse,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &transformResult{
|
|
||||||
code: string(result.Code),
|
|
||||||
errors: result.Errors,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
_ "reichard.io/poiesis/internal/standard"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"modernc.org/quickjs"
|
||||||
|
"reichard.io/poiesis/internal/functions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TestArgs struct {
|
||||||
|
Field1 string `json:"field1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TestArgs) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecuteTypeScript(t *testing.T) {
|
func TestExecuteTypeScript(t *testing.T) {
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
rt, err := New(context.Background())
|
rt, err := New(context.Background(), WithStderr(&stderr), WithStdout(&stdout))
|
||||||
assert.NoError(t, err, "Expected no error")
|
assert.NoError(t, err, "Expected no error")
|
||||||
|
|
||||||
err = rt.RunFile("../../test_data/test.ts", &stdout, &stderr)
|
err = rt.RunFile("../../test_data/test.ts")
|
||||||
|
|
||||||
assert.NoError(t, err, "Expected no error")
|
assert.NoError(t, err, "Expected no error")
|
||||||
assert.Empty(t, stderr.String(), "Expected no error output")
|
assert.Empty(t, stderr.String(), "Expected no error output")
|
||||||
@@ -34,17 +42,47 @@ func TestExecuteTypeScript(t *testing.T) {
|
|||||||
assert.GreaterOrEqual(t, len(lines), 3, "Should have at least 3 output lines")
|
assert.GreaterOrEqual(t, len(lines), 3, "Should have at least 3 output lines")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchBuiltinIntegration(t *testing.T) {
|
func TestAsyncFunctionResolution(t *testing.T) {
|
||||||
rt, err := New(context.Background())
|
functions.RegisterAsyncFunction("resolveTest", func(_ context.Context, args TestArgs) (string, error) {
|
||||||
assert.NoError(t, err, "Expected no error")
|
return "test-result", nil
|
||||||
|
})
|
||||||
|
|
||||||
tsContent := `
|
r, err := New(context.Background())
|
||||||
const result = add({a: 5, b: 10});
|
|
||||||
console.log("Result:", result);
|
|
||||||
`
|
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
err = rt.RunCode(tsContent, &stdout, &stderr)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, stdout.String(), "Result:")
|
|
||||||
|
result, err := r.vm.Eval(`resolveTest("hello")`, quickjs.EvalGlobal)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncFunctionRejection(t *testing.T) {
|
||||||
|
functions.RegisterAsyncFunction("rejectTest", func(_ context.Context, args TestArgs) (string, error) {
|
||||||
|
return "", assert.AnError
|
||||||
|
})
|
||||||
|
|
||||||
|
r, err := New(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, err := r.vm.Eval(`rejectTest({field1: "hello"})`, quickjs.EvalGlobal)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonPromise(t *testing.T) {
|
||||||
|
functions.RegisterFunction("nonPromiseTest", func(_ context.Context, args TestArgs) (string, error) {
|
||||||
|
return "sync-result", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
r, err := New(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, err := r.vm.Eval(`nonPromiseTest({field1: "hello"})`, quickjs.EvalGlobal)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"modernc.org/quickjs"
|
|
||||||
"reichard.io/poiesis/internal/functions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFetch(t *testing.T) {
|
func TestFetch(t *testing.T) {
|
||||||
@@ -98,48 +96,3 @@ func TestFetchDefaults(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, result.OK)
|
assert.True(t, result.OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchReturnsPromise(t *testing.T) {
|
|
||||||
vm, err := quickjs.NewVM()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = vm.Close()
|
|
||||||
}()
|
|
||||||
vm.SetCanBlock(true)
|
|
||||||
|
|
||||||
functions.RegisterBuiltins(context.Background(), vm)
|
|
||||||
|
|
||||||
result, err := vm.Eval(`fetch({input: "https://example.com"})`, quickjs.EvalGlobal)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchAsyncAwait(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
vm, err := quickjs.NewVM()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = vm.Close()
|
|
||||||
}()
|
|
||||||
vm.SetCanBlock(true)
|
|
||||||
|
|
||||||
functions.RegisterBuiltins(context.Background(), vm)
|
|
||||||
|
|
||||||
result, err := vm.Eval(`fetch({input: "`+server.URL+`"})`, quickjs.EvalGlobal)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if obj, ok := result.(*quickjs.Object); ok {
|
|
||||||
var arr []any
|
|
||||||
if err := obj.Into(&arr); err == nil && len(arr) > 0 {
|
|
||||||
if response, ok := arr[0].(map[string]any); ok {
|
|
||||||
assert.True(t, response["ok"].(bool))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
var done = false;
|
var done = false;
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
console.log(11);
|
const response = await fetch("https://httpbin.org/get");
|
||||||
const response = fetch("https://httpbin.org/get");
|
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
||||||
console.log("OK:", response.ok);
|
console.log("OK:", response.ok);
|
||||||
@@ -10,6 +9,7 @@ async function main() {
|
|||||||
console.log("Body:", response.body);
|
console.log("Body:", response.body);
|
||||||
console.log("Content-Type:", response.headers["content-type"]);
|
console.log("Content-Type:", response.headers["content-type"]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log("error");
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
done = true;
|
done = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user