diff --git a/AGENTS.md b/AGENTS.md index f8f0d2d..8a3e5b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,39 +26,37 @@ reichard.io/poiesis/ │ ├── runtime/ # Runtime management, transpilation, execution │ │ ├── runtime.go │ │ └── runtime_test.go -│ ├── builtin/ # Builtin registration framework -│ │ ├── types.go +│ ├── functions/ # Function registration framework +│ │ ├── collector.go │ │ ├── registry.go -│ │ ├── wrapper.go -│ │ ├── convert.go +│ │ ├── types.go │ │ ├── typescript.go -│ │ └── builtin_test.go -│ └── standard/ # Standard builtin implementations +│ │ ├── typescript_test.go +│ │ └── functions_test.go +│ └── stdlib/ # Standard library implementations │ ├── fetch.go -│ ├── fetch_test.go -│ └── fetch_promise_test.go -└── test_data/ # Test TypeScript files +│ └── fetch_test.go ``` ## Key Packages - `reichard.io/poiesis/internal/runtime` - Runtime management, TypeScript transpilation, JavaScript execution -- `reichard.io/poiesis/internal/builtin` - Generic builtin registration framework (sync/async wrappers, automatic JS/Go conversion via JSON, type definition generation) -- `reichard.io/poiesis/internal/standard` - Standard builtin implementations (fetch, add, greet, etc.) +- `reichard.io/poiesis/internal/functions` - Generic function registration framework (sync/async wrappers, automatic JS/Go conversion via JSON, type definition generation) +- `reichard.io/poiesis/internal/stdlib` - Standard library implementations (fetch) -## Builtin System +## Function System ### Registration -Two types of builtins: -- **Sync**: `RegisterBuiltin[T, R](name, fn)` - executes synchronously, returns value -- **Async**: `RegisterAsyncBuiltin[T, R](name, fn)` - runs in goroutine, returns Promise +Two types of functions: +- **Sync**: `RegisterFunction[T, R](name, fn)` - executes synchronously, returns value +- **Async**: `RegisterAsyncFunction[T, R](name, fn)` - runs in goroutine, returns Promise ### Requirements - Args must be a struct implementing `Args` interface with `Validate() error` method - Use JSON tags for TypeScript type definitions -- Async builtins automatically generate `Promise` return types +- Async functions automatically generate `Promise` return types ### Example @@ -74,11 +72,11 @@ func Add(_ context.Context, args AddArgs) (int, error) { return args.A + args.B, nil } -// Register sync builtin -builtin.RegisterBuiltin[AddArgs, int]("add", Add) +// Register sync function +functions.RegisterFunction[AddArgs, int]("add", Add) -// Register async builtin -builtin.RegisterAsyncBuiltin[FetchArgs, *FetchResult]("fetch", Fetch) +// Register async function +functions.RegisterAsyncFunction[FetchArgs, *FetchResult]("fetch", Fetch) ``` ## Testing Patterns @@ -87,6 +85,7 @@ builtin.RegisterAsyncBuiltin[FetchArgs, *FetchResult]("fetch", Fetch) - **Assertions**: `github.com/stretchr/testify/assert` and `require` - **Linting**: `golangci-lint run` - must pass before committing - **Test organization**: Test files use `_test.go` suffix, test functions prefixed with `Test` +- **TypeScript test files**: Tests that require TypeScript files should create them inline using `os.CreateTemp()` instead of relying on external test files ## Dependencies diff --git a/internal/functions/functions_test.go b/internal/functions/functions_test.go deleted file mode 100644 index ed6f415..0000000 --- a/internal/functions/functions_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package functions - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type TestArgs struct { - Field1 string `json:"field1"` -} - -func (t TestArgs) Validate() error { - return nil -} - -func TestAsyncFunction(t *testing.T) { - RegisterAsyncFunction("testAsync", func(_ context.Context, args TestArgs) (string, error) { - return "result: " + args.Field1, nil - }) - - fn, ok := functionRegistry["testAsync"] - - require.True(t, ok, "testAsync should be registered") - assert.Contains(t, fn.Definition(), "Promise", "definition should include Promise") -} - -// TOOD: Test Normal Function diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 99c4486..d83c077 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -41,17 +41,16 @@ func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) { } func (r *Runtime) populateGlobals() error { - // Register Custom Functions for name, fn := range functions.GetRegisteredFunctions() { // Register Main Function if fn.IsAsync() { r.ctx.SetAsyncFunc(name, func(this *qjs.This) { qjsVal, err := callFunc(this, fn) if err != nil { - this.Promise().Reject(this.Context().NewError(err)) + _ = this.Promise().Reject(this.Context().NewError(err)) return } - this.Promise().Resolve(qjsVal) + _ = this.Promise().Resolve(qjsVal) }) } else { r.ctx.SetFunc(name, func(this *qjs.This) (*qjs.Value, error) { diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 1788cd9..0caac4d 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -3,6 +3,7 @@ package runtime import ( "bytes" "context" + "os" "strings" "testing" @@ -26,7 +27,44 @@ func TestExecuteTypeScript(t *testing.T) { rt, err := New(context.Background(), WithStderr(&stderr), WithStdout(&stdout)) assert.NoError(t, err, "Expected no error") - err = rt.RunFile("../../test_data/test.ts") + tsCode := `interface Person { + name: string; + age: number; + email: string; +} + +function greet(person: Person): string { + return "Hello, " + person.name + "! You are " + person.age + " years old."; +} + +const user: Person = { + name: "Alice", + age: 30, + email: "alice@example.com", +}; + +console.log(greet(user)); +console.log("Email: " + user.email); + +function calculateSum(a: number, b: number): number { + return a + b; +} + +console.log("Sum of 5 and 10 is: " + calculateSum(5, 10)); +` + + tmpFile, err := os.CreateTemp("", "*.ts") + assert.NoError(t, err, "Failed to create temp file") + t.Cleanup(func() { + _ = os.Remove(tmpFile.Name()) + }) + + _, err = tmpFile.WriteString(tsCode) + assert.NoError(t, err, "Failed to write to temp file") + err = tmpFile.Close() + assert.NoError(t, err, "Failed to close temp file") + + err = rt.RunFile(tmpFile.Name()) assert.NoError(t, err, "Expected no error") assert.Empty(t, stderr.String(), "Expected no error output") diff --git a/test_data/fetch-new.ts b/test_data/fetch-new.ts deleted file mode 100644 index e299263..0000000 --- a/test_data/fetch-new.ts +++ /dev/null @@ -1,14 +0,0 @@ -try { - console.log(1); - const response = fetch("https://httpbin.org/get"); - console.log(2); - console.log(response); - - console.log("OK:", response.ok); - console.log("Status:", response.status); - console.log("Body:", response.body); - console.log("Content-Type:", response.headers["content-type"]); -} catch (e) { - console.log(e.message); - console.log("exception"); -} diff --git a/test_data/fetch.ts b/test_data/fetch.ts deleted file mode 100644 index 6db13d6..0000000 --- a/test_data/fetch.ts +++ /dev/null @@ -1,16 +0,0 @@ -async function main() { - try { - const response = await fetch("https://httpbin.org/get"); - console.log(response); - - console.log("OK:", response.ok); - console.log("Status:", response.status); - console.log("Body:", response.body); - console.log("Content-Type:", response.headers["content-type"]); - } catch (e) { - console.log("error"); - console.log(e); - } -} - -main(); diff --git a/test_data/fetch_demo.ts b/test_data/fetch_demo.ts deleted file mode 100644 index 2f5ea84..0000000 --- a/test_data/fetch_demo.ts +++ /dev/null @@ -1,6 +0,0 @@ -const response = fetch("https://httpbin.org/get"); - -console.log("OK:", response.ok); -console.log("Status:", response.status); -console.log("Body:", response.text()); -console.log("Content-Type:", response.headers.get("content-type")); diff --git a/test_data/fetch_promise_test.ts b/test_data/fetch_promise_test.ts deleted file mode 100644 index debca69..0000000 --- a/test_data/fetch_promise_test.ts +++ /dev/null @@ -1,11 +0,0 @@ -async function logPromiseResult() { - try { - const response = await fetch({input: "https://httpbin.org/get"}); - console.log("Fetch successful, OK:", response.ok); - console.log("Status:", response.status); - } catch (error) { - console.log("Fetch failed:", error); - } -} - -logPromiseResult(); diff --git a/test_data/simple_builtins.ts b/test_data/simple_builtins.ts deleted file mode 100644 index e25b8c6..0000000 --- a/test_data/simple_builtins.ts +++ /dev/null @@ -1,9 +0,0 @@ -const sum = add(5, 10); -console.log("5 + 10 =", sum); - -const greeting = greet("World"); -console.log(greeting); - -const response = fetch("https://httpbin.org/get"); -console.log("Fetch OK:", response.ok); -console.log("Fetch Status:", response.status); diff --git a/test_data/test.ts b/test_data/test.ts deleted file mode 100644 index f467c10..0000000 --- a/test_data/test.ts +++ /dev/null @@ -1,24 +0,0 @@ -interface Person { - name: string; - age: number; - email: string; -} - -function greet(person: Person): string { - return `Hello, ${person.name}! You are ${person.age} years old.`; -} - -const user: Person = { - name: "Alice", - age: 30, - email: "alice@example.com", -}; - -console.log(greet(user)); -console.log(`Email: ${user.email}`); - -function calculateSum(a: number, b: number): number { - return a + b; -} - -console.log(`Sum of 5 and 10 is: ${calculateSum(5, 10)}`);