This commit is contained in:
2026-01-28 21:59:04 -05:00
parent dcd516d970
commit ffcb6f658b
10 changed files with 60 additions and 134 deletions

View File

@@ -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<R>` return types
- Async functions automatically generate `Promise<R>` 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

View File

@@ -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<string>", "definition should include Promise<string>")
}
// TOOD: Test Normal Function

View File

@@ -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) {

View File

@@ -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")

View File

@@ -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");
}

View File

@@ -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();

View File

@@ -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"));

View File

@@ -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();

View File

@@ -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);

View File

@@ -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)}`);