diff --git a/internal/builtin/wrapper.go b/internal/builtin/wrapper.go deleted file mode 100644 index 5d9ee61..0000000 --- a/internal/builtin/wrapper.go +++ /dev/null @@ -1 +0,0 @@ -package builtin diff --git a/internal/builtin/collector.go b/internal/functions/collector.go similarity index 99% rename from internal/builtin/collector.go rename to internal/functions/collector.go index 58bfab4..5352698 100644 --- a/internal/builtin/collector.go +++ b/internal/functions/collector.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "fmt" diff --git a/internal/builtin/builtin_test.go b/internal/functions/functions_test.go similarity index 66% rename from internal/builtin/builtin_test.go rename to internal/functions/functions_test.go index 254fbb4..a44165b 100644 --- a/internal/builtin/builtin_test.go +++ b/internal/functions/functions_test.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "context" @@ -18,21 +18,21 @@ func (t TestArgs) Validate() error { return nil } -func TestAsyncBuiltin(t *testing.T) { - RegisterAsyncBuiltin("testAsync", func(_ context.Context, args TestArgs) (string, error) { +func TestAsyncFunction(t *testing.T) { + RegisterAsyncFunction("testAsync", func(_ context.Context, args TestArgs) (string, error) { return "result: " + args.Field1, nil }) registryMutex.RLock() - builtin, ok := builtinRegistry["testAsync"] + fn, ok := functionRegistry["testAsync"] registryMutex.RUnlock() require.True(t, ok, "testAsync should be registered") - assert.Contains(t, builtin.Definition(), "Promise", "definition should include Promise") + assert.Contains(t, fn.Definition(), "Promise", "definition should include Promise") } -func TestAsyncBuiltinResolution(t *testing.T) { - RegisterAsyncBuiltin("resolveTest", func(_ context.Context, args TestArgs) (string, error) { +func TestAsyncFunctionResolution(t *testing.T) { + RegisterAsyncFunction("resolveTest", func(_ context.Context, args TestArgs) (string, error) { return "test-result", nil }) @@ -43,15 +43,15 @@ func TestAsyncBuiltinResolution(t *testing.T) { }() vm.SetCanBlock(true) - RegisterBuiltins(context.Background(), vm) + RegisterFunctions(context.Background(), vm) result, err := vm.Eval(`resolveTest("hello")`, quickjs.EvalGlobal) require.NoError(t, err) assert.NotNil(t, result) } -func TestAsyncBuiltinRejection(t *testing.T) { - RegisterAsyncBuiltin("rejectTest", func(_ context.Context, args TestArgs) (string, error) { +func TestAsyncFunctionRejection(t *testing.T) { + RegisterAsyncFunction("rejectTest", func(_ context.Context, args TestArgs) (string, error) { return "", assert.AnError }) @@ -62,7 +62,7 @@ func TestAsyncBuiltinRejection(t *testing.T) { }() vm.SetCanBlock(true) - RegisterBuiltins(context.Background(), vm) + RegisterFunctions(context.Background(), vm) result, err := vm.Eval(`rejectTest({field1: "hello"})`, quickjs.EvalGlobal) require.NoError(t, err) @@ -70,7 +70,7 @@ func TestAsyncBuiltinRejection(t *testing.T) { } func TestNonPromise(t *testing.T) { - RegisterBuiltin("nonPromiseTest", func(_ context.Context, args TestArgs) (string, error) { + RegisterFunction("nonPromiseTest", func(_ context.Context, args TestArgs) (string, error) { return "sync-result", nil }) @@ -81,7 +81,7 @@ func TestNonPromise(t *testing.T) { }() vm.SetCanBlock(true) - RegisterBuiltins(context.Background(), vm) + RegisterFunctions(context.Background(), vm) result, err := vm.Eval(`nonPromiseTest({field1: "hello"})`, quickjs.EvalGlobal) require.NoError(t, err) diff --git a/internal/builtin/registry.go b/internal/functions/registry.go similarity index 54% rename from internal/builtin/registry.go rename to internal/functions/registry.go index 63e656d..48af53f 100644 --- a/internal/builtin/registry.go +++ b/internal/functions/registry.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "fmt" @@ -8,12 +8,12 @@ import ( ) var ( - builtinRegistry = make(map[string]Builtin) - registryMutex sync.RWMutex - collector *typeCollector + functionRegistry = make(map[string]Function) + registryMutex sync.RWMutex + collector *typeCollector ) -func registerBuiltin[A Args, R any](name string, isAsync bool, fn Func[A, R]) { +func registerFunction[A Args, R any](name string, isAsync bool, fn RawFunc[A, R]) { registryMutex.Lock() defer registryMutex.Unlock() @@ -23,14 +23,14 @@ func registerBuiltin[A Args, R any](name string, isAsync bool, fn Func[A, R]) { tType := reflect.TypeFor[A]() if tType.Kind() != reflect.Struct { - panic(fmt.Sprintf("builtin %s: argument must be a struct type, got %v", name, tType)) + panic(fmt.Sprintf("function %s: argument must be a struct type, got %v", name, tType)) } fnType := reflect.TypeOf(fn) types := collector.collectTypes(tType, fnType) paramTypes := collector.getParamTypes() - builtinRegistry[name] = &builtinImpl[A, R]{ + functionRegistry[name] = &functionImpl[A, R]{ name: name, fn: fn, types: types, @@ -38,7 +38,7 @@ func registerBuiltin[A Args, R any](name string, isAsync bool, fn Func[A, R]) { } } -func GetBuiltinsDeclarations() string { +func GetFunctionDeclarations() string { registryMutex.RLock() defer registryMutex.RUnlock() @@ -46,14 +46,14 @@ func GetBuiltinsDeclarations() string { var typeDefs []string var functionDecls []string - for _, builtin := range builtinRegistry { - for _, t := range builtin.Types() { + for _, fn := range functionRegistry { + for _, t := range fn.Types() { if !typeDefinitions[t] { typeDefinitions[t] = true typeDefs = append(typeDefs, t) } } - functionDecls = append(functionDecls, builtin.Definition()) + functionDecls = append(functionDecls, fn.Definition()) } result := strings.Join(typeDefs, "\n\n") @@ -65,14 +65,14 @@ func GetBuiltinsDeclarations() string { return result } -func RegisterBuiltin[T Args, R any](name string, fn Func[T, R]) { - registerBuiltin(name, false, fn) +func GetRegisteredFunctions() map[string]Function { + return functionRegistry } -func RegisterAsyncBuiltin[T Args, R any](name string, fn Func[T, R]) { - registerBuiltin(name, true, fn) +func RegisterFunction[T Args, R any](name string, fn RawFunc[T, R]) { + registerFunction(name, false, fn) } -func GetBuiltins() map[string]Builtin { - return builtinRegistry +func RegisterAsyncFunction[T Args, R any](name string, fn RawFunc[T, R]) { + registerFunction(name, true, fn) } diff --git a/internal/builtin/types.go b/internal/functions/types.go similarity index 71% rename from internal/builtin/types.go rename to internal/functions/types.go index d2388b2..6d14b7e 100644 --- a/internal/builtin/types.go +++ b/internal/functions/types.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "context" @@ -6,39 +6,39 @@ import ( "reflect" ) -type Builtin interface { +type Function interface { Name() string Types() []string Definition() string WrapFn(context.Context) func(...any) (any, error) } -type Func[A Args, R any] func(ctx context.Context, args A) (R, error) +type RawFunc[A Args, R any] func(context.Context, A) (R, error) type Args interface { Validate() error } -type builtinImpl[A Args, R any] struct { +type functionImpl[A Args, R any] struct { name string - fn Func[A, R] + fn RawFunc[A, R] definition string types []string } -func (b *builtinImpl[A, R]) Name() string { +func (b *functionImpl[A, R]) Name() string { return b.name } -func (b *builtinImpl[A, R]) Types() []string { +func (b *functionImpl[A, R]) Types() []string { return b.types } -func (b *builtinImpl[A, R]) Definition() string { +func (b *functionImpl[A, R]) Definition() string { return b.definition } -func (b *builtinImpl[A, R]) WrapFn(ctx context.Context) func(...any) (any, error) { +func (b *functionImpl[A, R]) WrapFn(ctx context.Context) func(...any) (any, error) { return func(allArgs ...any) (any, error) { // Populate Arguments var fnArgs A diff --git a/internal/builtin/typescript.go b/internal/functions/typescript.go similarity index 99% rename from internal/builtin/typescript.go rename to internal/functions/typescript.go index bb1d1ad..966677a 100644 --- a/internal/builtin/typescript.go +++ b/internal/functions/typescript.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "fmt" diff --git a/internal/builtin/typescript_test.go b/internal/functions/typescript_test.go similarity index 75% rename from internal/builtin/typescript_test.go rename to internal/functions/typescript_test.go index 84b16fe..9ca90f8 100644 --- a/internal/builtin/typescript_test.go +++ b/internal/functions/typescript_test.go @@ -1,4 +1,4 @@ -package builtin +package functions import ( "context" @@ -18,11 +18,11 @@ func (t TestBasicArgs) Validate() error { return nil } func TestBasicType(t *testing.T) { resetRegistry() - RegisterBuiltin[TestBasicArgs, string]("basic", func(ctx context.Context, args TestBasicArgs) (string, error) { + RegisterFunction[TestBasicArgs, string]("basic", func(ctx context.Context, args TestBasicArgs) (string, error) { return args.Name, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function basic(name: string, age: number): string;") assert.Contains(t, defs, "interface TestBasicArgs") } @@ -30,7 +30,7 @@ func TestBasicType(t *testing.T) { func resetRegistry() { registryLock.Lock() defer registryLock.Unlock() - builtinRegistry = make(map[string]Builtin) + functionRegistry = make(map[string]Function) } var ( @@ -47,11 +47,11 @@ func (t TestComplexArgs) Validate() error { return nil } func TestComplexTypes(t *testing.T) { resetRegistry() - RegisterBuiltin[TestComplexArgs, bool]("complex", func(ctx context.Context, args TestComplexArgs) (bool, error) { + RegisterFunction[TestComplexArgs, bool]("complex", func(ctx context.Context, args TestComplexArgs) (bool, error) { return args.Flag, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function complex(items: number[], data: Record, flag: boolean): boolean;") } @@ -66,11 +66,11 @@ func (t TestNestedArgs) Validate() error { return nil } func TestNestedStruct(t *testing.T) { resetRegistry() - RegisterBuiltin[TestNestedArgs, string]("nested", func(ctx context.Context, args TestNestedArgs) (string, error) { + RegisterFunction[TestNestedArgs, string]("nested", func(ctx context.Context, args TestNestedArgs) (string, error) { return args.User.FirstName, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function nested(user: {}): string;") } @@ -84,11 +84,11 @@ func (t TestOptionalArgs) Validate() error { return nil } func TestOptionalFields(t *testing.T) { resetRegistry() - RegisterBuiltin[TestOptionalArgs, string]("optional", func(ctx context.Context, args TestOptionalArgs) (string, error) { + RegisterFunction[TestOptionalArgs, string]("optional", func(ctx context.Context, args TestOptionalArgs) (string, error) { return args.Name, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function optional(name: string, age?: number | null, score?: number | null): string;") } @@ -105,11 +105,11 @@ func (t TestResultArgs) Validate() error { return nil } func TestResultStruct(t *testing.T) { resetRegistry() - RegisterBuiltin[TestResultArgs, TestResult]("result", func(ctx context.Context, args TestResultArgs) (TestResult, error) { + RegisterFunction[TestResultArgs, TestResult]("result", func(ctx context.Context, args TestResultArgs) (TestResult, error) { return TestResult{ID: 1}, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function result(input: string): TestResult;") assert.Contains(t, defs, "interface TestResult {id: number; data: number[]}") } @@ -126,11 +126,11 @@ type TestAsyncResult struct { func TestAsyncPromise(t *testing.T) { resetRegistry() - RegisterAsyncBuiltin[TestAsyncArgs, *TestAsyncStatus]("async", func(ctx context.Context, args TestAsyncArgs) (*TestAsyncStatus, error) { + RegisterAsyncFunction[TestAsyncArgs, *TestAsyncStatus]("async", func(ctx context.Context, args TestAsyncArgs) (*TestAsyncStatus, error) { return &TestAsyncStatus{Code: 200}, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function async(url: string): Promise;") assert.Contains(t, defs, "interface TestAsyncStatus") } @@ -151,11 +151,11 @@ func (t TestNestedPointerArgs) Validate() error { return nil } func TestNestedPointerInResult(t *testing.T) { resetRegistry() - RegisterBuiltin[TestNestedPointerArgs, *TestNestedPointerResult]("pointerResult", func(ctx context.Context, args TestNestedPointerArgs) (*TestNestedPointerResult, error) { + RegisterFunction[TestNestedPointerArgs, *TestNestedPointerResult]("pointerResult", func(ctx context.Context, args TestNestedPointerArgs) (*TestNestedPointerResult, error) { return &TestNestedPointerResult{Value: "test"}, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function pointerResult(id: number): TestNestedPointerResult | null;") } @@ -167,11 +167,11 @@ func (t TestUintArgs) Validate() error { return nil } func TestUintType(t *testing.T) { resetRegistry() - RegisterBuiltin[TestUintArgs, uint]("uint", func(ctx context.Context, args TestUintArgs) (uint, error) { + RegisterFunction[TestUintArgs, uint]("uint", func(ctx context.Context, args TestUintArgs) (uint, error) { return args.Value, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function uint(value: number): number;") } @@ -183,11 +183,11 @@ func (t TestFloatArgs) Validate() error { return nil } func TestFloatType(t *testing.T) { resetRegistry() - RegisterBuiltin[TestFloatArgs, float32]("float", func(ctx context.Context, args TestFloatArgs) (float32, error) { + RegisterFunction[TestFloatArgs, float32]("float", func(ctx context.Context, args TestFloatArgs) (float32, error) { return float32(args.Amount), nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function float(amount: number): number;") } @@ -201,11 +201,11 @@ func (t TestPointerInArgs) Validate() error { return nil } func TestNestedPointerStruct(t *testing.T) { resetRegistry() - RegisterBuiltin[TestPointerInArgs, string]("nestedPointer", func(ctx context.Context, args TestPointerInArgs) (string, error) { + RegisterFunction[TestPointerInArgs, string]("nestedPointer", func(ctx context.Context, args TestPointerInArgs) (string, error) { return "test", nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function nestedPointer(user?: {} | null): string;") } @@ -217,11 +217,11 @@ func (t TestErrorOnlyArgs) Validate() error { return nil } func TestErrorOnlyReturn(t *testing.T) { resetRegistry() - RegisterBuiltin[TestErrorOnlyArgs, struct{}]("errorOnly", func(ctx context.Context, args TestErrorOnlyArgs) (struct{}, error) { + RegisterFunction[TestErrorOnlyArgs, struct{}]("errorOnly", func(ctx context.Context, args TestErrorOnlyArgs) (struct{}, error) { return struct{}{}, nil }) - defs := GetBuiltinsDeclarations() + defs := GetFunctionDeclarations() assert.Contains(t, defs, "declare function errorOnly(input: string): {};") } diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index cab9d71..6e1c4bc 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -8,7 +8,7 @@ import ( "github.com/evanw/esbuild/pkg/api" "modernc.org/quickjs" - "reichard.io/poiesis/internal/builtin" + "reichard.io/poiesis/internal/functions" ) type Runtime struct { @@ -59,7 +59,7 @@ func (r *Runtime) populateGlobals() error { } // Register Custom Functions - for name, builtin := range builtin.GetBuiltins() { + for name, builtin := range functions.GetRegisteredFunctions() { // Register Main Function if err := r.vm.RegisterFunc(name, builtin.WrapFn(r.ctx), false); err != nil { return err diff --git a/internal/standard/fetch_promise_test.go b/internal/standard/fetch_promise_test.go deleted file mode 100644 index d550653..0000000 --- a/internal/standard/fetch_promise_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package standard - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "modernc.org/quickjs" - - "reichard.io/poiesis/internal/builtin" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFetchReturnsPromise(t *testing.T) { - vm, err := quickjs.NewVM() - require.NoError(t, err) - defer func() { - _ = vm.Close() - }() - vm.SetCanBlock(true) - - builtin.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) - - builtin.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)) - } - } - } -} diff --git a/internal/standard/fetch.go b/internal/stdlib/fetch.go similarity index 84% rename from internal/standard/fetch.go rename to internal/stdlib/fetch.go index 1c02b2e..0557191 100644 --- a/internal/standard/fetch.go +++ b/internal/stdlib/fetch.go @@ -1,4 +1,4 @@ -package standard +package stdlib import ( "context" @@ -8,9 +8,13 @@ import ( "net/http" "strings" - "reichard.io/poiesis/internal/builtin" + "reichard.io/poiesis/internal/functions" ) +func init() { + functions.RegisterAsyncFunction("fetch", Fetch) +} + type FetchArgs struct { Input string `json:"input"` Init *RequestInit `json:"init,omitempty"` @@ -106,17 +110,3 @@ func Fetch(_ context.Context, args FetchArgs) (Response, error) { Headers: resultHeaders, }, nil } - -func Add(_ context.Context, args AddArgs) (int, error) { - return args.A + args.B, nil -} - -func Greet(_ context.Context, args GreetArgs) (string, error) { - return fmt.Sprintf("Hello, %s!", args.Name), nil -} - -func init() { - builtin.RegisterAsyncBuiltin("fetch", Fetch) - builtin.RegisterBuiltin("add", Add) - builtin.RegisterBuiltin("greet", Greet) -} diff --git a/internal/standard/fetch_test.go b/internal/stdlib/fetch_test.go similarity index 73% rename from internal/standard/fetch_test.go rename to internal/stdlib/fetch_test.go index 2a7b605..065e3f1 100644 --- a/internal/standard/fetch_test.go +++ b/internal/stdlib/fetch_test.go @@ -1,4 +1,4 @@ -package standard +package stdlib import ( "context" @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "modernc.org/quickjs" + "reichard.io/poiesis/internal/functions" ) func TestFetch(t *testing.T) { @@ -97,24 +99,47 @@ func TestFetchDefaults(t *testing.T) { assert.True(t, result.OK) } -func TestAdd(t *testing.T) { - ctx := context.Background() - result, err := Add(ctx, AddArgs{A: 5, B: 10}) +func TestFetchReturnsPromise(t *testing.T) { + vm, err := quickjs.NewVM() require.NoError(t, err) - assert.Equal(t, 15, result) + defer func() { + _ = vm.Close() + }() + vm.SetCanBlock(true) - result, err = Add(ctx, AddArgs{A: -3, B: 7}) + functions.RegisterBuiltins(context.Background(), vm) + + result, err := vm.Eval(`fetch({input: "https://example.com"})`, quickjs.EvalGlobal) require.NoError(t, err) - assert.Equal(t, 4, result) + assert.NotNil(t, result) } -func TestGreet(t *testing.T) { - ctx := context.Background() - result, err := Greet(ctx, GreetArgs{Name: "World"}) - require.NoError(t, err) - assert.Equal(t, "Hello, World!", 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() - result, err = Greet(ctx, GreetArgs{Name: "Alice"}) + vm, err := quickjs.NewVM() require.NoError(t, err) - assert.Equal(t, "Hello, Alice!", result) + 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)) + } + } + } }