diff --git a/AGENTS.md b/AGENTS.md index 8a3e5b4..c6c2517 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,15 @@ Two types of functions: - Use JSON tags for TypeScript type definitions - Async functions automatically generate `Promise` return types +### Calling Convention + +**Important**: Functions have different calling conventions on Go vs JavaScript sides: + +- **Go side**: Function receives a **single argument struct** with all parameters +- **JavaScript side**: Function is called with **individual arguments** matching the struct fields (in field order) + +Fields are ordered by their position in the struct, with the generated TypeScript signature using those field names as argument names. + ### Example ```go diff --git a/README.md b/README.md index eb938e1..ba5e5e8 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,37 @@ That's it! The framework automatically: - Generates TypeScript definitions - Manages the qjs integration +### Calling Convention + +**Important**: There's an important difference between how functions are defined in Go versus how they're called in JavaScript: + +- **Go side**: The function receives a **single argument struct** containing all parameters +- **JavaScript side**: The function is called with the **struct fields as individual arguments** (in the order they appear in the struct) + +```go +// Go: Single struct argument +type AddArgs struct { + A int `json:"a"` + B int `json:"b"` +} + +func Add(_ context.Context, args AddArgs) (int, error) { + return args.A + args.B, nil +} +``` + +```typescript +// JavaScript: Individual arguments (not an object!) +const result = add(5, 10); // NOT add({ a: 5, b: 10 }) +``` + +The framework extracts the JSON tags from the struct fields and uses them to generate the correct TypeScript function signature. + ### Example ```typescript -// TypeScript code -const response = fetch({input: "https://httpbin.org/get"}); +// TypeScript code - call with individual arguments matching struct fields +const response = fetch("https://httpbin.org/get"); console.log("OK:", response.ok); console.log("Status:", response.status); console.log("Body:", response.body); diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 0caac4d..f78b045 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -9,6 +9,7 @@ import ( "github.com/fastschema/qjs" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "reichard.io/poiesis/internal/functions" ) @@ -86,17 +87,14 @@ func TestAsyncFunctionResolution(t *testing.T) { }) r, err := New(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) - result, err := r.ctx.Eval("test.js", qjs.Code(`(async () => { return await resolveTest({field1: "hello"}); })()`)) - if err == nil && result != nil { - defer result.Free() - val, err := result.Await() - assert.NoError(t, err) - assert.Equal(t, "test-result", val.String()) - } else { - t.Logf("Skipping async test - error: %v", err) - } + // Async functions need to be awaited in an async context + result, err := r.ctx.Eval("test.js", qjs.Code(`async function run() { return await resolveTest("hello"); }; run()`)) + require.NoError(t, err) + require.NotNil(t, result) + defer result.Free() + assert.Equal(t, "test-result", result.String()) } func TestAsyncFunctionRejection(t *testing.T) { @@ -105,9 +103,10 @@ func TestAsyncFunctionRejection(t *testing.T) { }) r, err := New(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) - _, err = r.ctx.Eval("test.js", qjs.Code(`(async () => { return await rejectTest({field1: "hello"}); })()`)) + // Rejected promises throw when awaited + _, err = r.ctx.Eval("test.js", qjs.Code(`async function run() { return await rejectTest("hello"); }; run()`)) assert.Error(t, err) } diff --git a/internal/stdlib/fetch_test.go b/internal/stdlib/fetch_test.go index 931d3c7..7d009f4 100644 --- a/internal/stdlib/fetch_test.go +++ b/internal/stdlib/fetch_test.go @@ -32,10 +32,15 @@ func TestFetch(t *testing.T) { } func TestFetchHTTPBin(t *testing.T) { - t.Skip("httpbin.org test is flaky") ctx := context.Background() + 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(`{"args":{}}`)) + })) + defer server.Close() - result, err := Fetch(ctx, FetchArgs{Input: "https://httpbin.org/get"}) + result, err := Fetch(ctx, FetchArgs{Input: server.URL}) require.NoError(t, err) assert.True(t, result.OK)