more cleanup

This commit is contained in:
2026-01-28 22:28:42 -05:00
parent ffcb6f658b
commit e04fe8cef3
4 changed files with 84 additions and 71 deletions

105
README.md
View File

@@ -1,6 +1,6 @@
# Poiesis # Poiesis
A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with goja, with an extensible builtin system. A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with qjs, with an extensible function system.
## Project Structure ## Project Structure
@@ -10,35 +10,37 @@ reichard.io/poiesis/
│ └── poiesis/ # CLI application entry point │ └── poiesis/ # CLI application entry point
│ └── main.go │ └── main.go
├── internal/ ├── internal/
── runtime/ ── runtime/ # Runtime management, transpilation, execution
├── pkg/ ├── runtime.go # Core runtime, transpilation, execution
│ └── builtin/ # Builtin framework (framework only) │ │ └── runtime_test.go # Runtime tests
└── builtin.go # Registration system & type conversion ├── functions/ # Function registration framework
├── standard/ # Standard builtin implementations ├── registry.go # Registration system
│ ├── fetch.go # HTTP fetch builtin │ │ ├── types.go # Core interfaces and types
── fetch_test.go # Tests for fetch │ │ ── typescript.go # TypeScript definition generation
├── runtime.go # Transpilation & execution ├── collector.go # Type collection utilities
└── runtime_test.go # Runtime tests └── typescript_test.go # Type system tests
│ └── stdlib/ # Standard library implementations
│ ├── fetch.go # HTTP fetch implementation
│ └── fetch_test.go # Fetch tests
``` ```
## Architecture ## Architecture
The project is cleanly separated into three packages: The project is cleanly separated into three packages:
1. **`internal/runtime/pkg/builtin`** - The framework for registering builtins and type conversion 1. **`internal/runtime`** - Runtime management
- Generic registration with automatic type inference
- Bidirectional Go ↔ JavaScript type conversion
- No builtin implementations (pure framework)
2. **`internal/runtime/standard`** - Standard builtin implementations
- `fetch`, `add`, `greet`
- Custom type converters for complex types
- Independent and easily extensible
3. **`internal/runtime`** - Runtime management
- TypeScript transpilation with esbuild - TypeScript transpilation with esbuild
- JavaScript execution with goja - JavaScript execution with qjs
- Automatically imports and registers standard builtins - Automatic function registration and execution
2. **`internal/functions`** - Generic function registration framework
- Type-safe registration with generics
- Bidirectional Go ↔ JavaScript type conversion
- Automatic TypeScript declaration generation
3. **`internal/stdlib`** - Standard library implementations
- `fetch` - HTTP requests
- Extensible for additional standard functions
## Installation & Build ## Installation & Build
@@ -57,55 +59,74 @@ golangci-lint run
```bash ```bash
poiesis <typescript-file> poiesis <typescript-file>
poiesis -print-types
``` ```
## Builtin System ## Function System
The builtin system allows you to easily expose Go functions to TypeScript/JavaScript. The function system allows you to easily expose Go functions to TypeScript/JavaScript.
### Adding a Builtin ### Adding a Function
Just write a Go function and register it: Just write a Go function and register it:
```go ```go
// Your function package mystdlib
func add(a, b int) int {
return a + b import (
"context"
"reichard.io/poiesis/internal/functions"
)
type AddArgs struct {
A int `json:"a"`
B int `json:"b"`
}
func (a AddArgs) Validate() error {
return nil
}
func Add(_ context.Context, args AddArgs) (int, error) {
return args.A + args.B, nil
} }
// Register it
func init() { func init() {
builtin.RegisterBuiltin("add", add) functions.RegisterFunction[AddArgs, int]("add", Add)
} }
``` ```
That's it! The framework automatically: That's it! The framework automatically:
- Converts JavaScript values to Go types
- Converts TypeScript values to Go types
- Handles errors (panics as JS errors) - Handles errors (panics as JS errors)
- Generates TypeScript definitions - Generates TypeScript definitions
- Manages the goja integration - Manages the qjs integration
### Example ### Example
```typescript ```typescript
// TypeScript code // TypeScript code
console.log("5 + 10 =", add(5, 10)); const response = fetch({input: "https://httpbin.org/get"});
const response = fetch("https://httpbin.org/get");
console.log("OK:", response.ok); console.log("OK:", response.ok);
console.log("Status:", response.status); console.log("Status:", response.status);
console.log("Body:", response.text()); console.log("Body:", response.body);
``` ```
### Built-in Functions ### Built-in Functions
- `fetch(url, options?)` - HTTP requests - `fetch(options)` - HTTP requests
- `add(a, b)` - Simple arithmetic example - `options.input` (string) - URL to fetch
- `greet(name)` - String manipulation example - `options.init` (object) - Optional init object with `method`, `headers`, `body`
## Dependencies ## Dependencies
- `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation - `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation
- `github.com/dop251/goja` - JavaScript execution - `github.com/fastschema/qjs` - JavaScript execution (QuickJS)
- `github.com/stretchr/testify/assert` - Test assertions - `github.com/stretchr/testify/assert` - Test assertions
## Development
- **Test framework**: Go's built-in `testing` package
- **Code style**: Follow standard Go conventions
- **Linting**: `golangci-lint run` - must pass before committing
- **TypeScript test files**: Tests that require TypeScript files should create them inline using `os.CreateTemp()` instead of relying on external test files

View File

@@ -67,7 +67,14 @@ func GetFunctionDeclarations() string {
} }
func GetRegisteredFunctions() map[string]Function { func GetRegisteredFunctions() map[string]Function {
return functionRegistry registryMutex.RLock()
defer registryMutex.RUnlock()
result := make(map[string]Function, len(functionRegistry))
for k, v := range functionRegistry {
result[k] = v
}
return result
} }
func RegisterFunction[T Args, R any](name string, fn GoFunc[T, R]) { func RegisterFunction[T Args, R any](name string, fn GoFunc[T, R]) {

View File

@@ -94,11 +94,14 @@ func (r *Runtime) transformCode(tsCode string) ([]byte, error) {
}) })
if len(result.Errors) > 0 { if len(result.Errors) > 0 {
var allErrs []string var b strings.Builder
for _, e := range result.Errors { for i, e := range result.Errors {
allErrs = append(allErrs, e.Text) if i > 0 {
b.WriteString(", ")
}
b.WriteString(e.Text)
} }
return nil, fmt.Errorf("transpilation failed: %s", strings.Join(allErrs, ", ")) return nil, fmt.Errorf("transpilation failed: %s", b.String())
} }
return result.Code, nil return result.Code, nil
@@ -112,7 +115,7 @@ func callFunc(this *qjs.This, fn functions.Function) (*qjs.Value, error) {
for i := range min(len(fnArgs), len(qjsArgs)) { for i := range min(len(fnArgs), len(qjsArgs)) {
rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i]) rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i])
if err != nil { if err != nil {
panic(err) return nil, fmt.Errorf("argument conversion failed: %w", err)
} }
allArgs = append(allArgs, rVal.Interface()) allArgs = append(allArgs, rVal.Interface())
} }

View File

@@ -31,9 +31,6 @@ type RequestInit struct {
} }
func (o *RequestInit) Validate() error { func (o *RequestInit) Validate() error {
if o.Method == "" {
o.Method = "GET"
}
return nil return nil
} }
@@ -44,35 +41,20 @@ type Response struct {
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
} }
type AddArgs struct { func Fetch(ctx context.Context, args FetchArgs) (Response, error) {
A int `json:"a"`
B int `json:"b"`
}
func (a AddArgs) Validate() error {
return nil
}
type GreetArgs struct {
Name string `json:"name"`
}
func (g GreetArgs) Validate() error {
return nil
}
func Fetch(_ context.Context, args FetchArgs) (Response, error) {
method := "GET" method := "GET"
headers := make(map[string]string) headers := make(map[string]string)
if args.Init != nil { if args.Init != nil {
method = args.Init.Method if args.Init.Method != "" {
method = args.Init.Method
}
if args.Init.Headers != nil { if args.Init.Headers != nil {
maps.Copy(headers, args.Init.Headers) maps.Copy(headers, args.Init.Headers)
} }
} }
req, err := http.NewRequest(method, args.Input, nil) req, err := http.NewRequestWithContext(ctx, method, args.Input, nil)
if err != nil { if err != nil {
return Response{}, fmt.Errorf("failed to create request: %w", err) return Response{}, fmt.Errorf("failed to create request: %w", err)
} }